DeyjaScript 8 : Modules

posted in Jemgine
Published September 27, 2011
Advertisement
I try to keep the ultimate goal of this project, to be used in a mud engine, in mind when I decide what features to implement next. So I decide that modules are next. Actually compiling a self-contained module is straightforward. I changed the 'ExecutionContext' into a 'ScriptEngine'. I gave it a global scope. Each module will get it's own scope, that is contained within the global scope. They can co-exist just fine, but, they can't actually refer to each other in anyway. I need some way for types to be visible to other modules. The syntax for referring to a type in another module looks similar to C++'s namespace resolution operator. ModuleName:TypeName. I decided not to overload the . operator the way C# does because it would complicate the syntax (There are cases where it's impossible for the parser to distinguish between member access and namespace access).
I added the 'typename' to the grammar, and everywhere the grammar currently used an identifier as a typename, I replace it. And then I need to actually use the additional information.


class TypenameNode : AstNode
{
List Tokens = new List();

public override void Init(Irony.Parsing.ParsingContext context, Irony.Parsing.ParseTreeNode treeNode)
{
base.Init(context, treeNode);
foreach (var child in treeNode.ChildNodes)
Tokens.Add(child.FindTokenAndGetText());
AsString = String.Join(":", Tokens.ToArray());
}

internal Scope findType(ScopeStack context)
{
var type = context.topScope.searchForType(Tokens.ToArray());
if (type == null) throw new CompileError("Could not find type " + Tokens[0]);
return type;
}
}

//Inside Scope
public Scope searchForType(String[] tokens)
{
var type = findType(tokens[0], true);
if (type == null) return null;
if (tokens.Length > 1)
{
for (int i = 1; i < tokens.Length; ++i)
{
type = type.findType(tokens, false);
if (type == null) return null;
}
}
return type;
}


The node is not compilable. In all the cases where identifier nodes were used as typenames before, there was never any attempt to compile them. The typename node will remember the typename as specified in the source. FindType first searches up through the scopes for the first token, and then from that type down until all tokens are matched. Right now there's no way to declare a type more than one level deep, so I don't need to bother looking any further, but the functionality is there. And then I have to change everything to use this new method. Thankfully, only a few nodes use typenames. Declarations, new statements, and the as operation. Several other nodes compare typenames, and there I run into a problem. They need to know the full typename, with the module specified.
I give Scopes a 'fullTypeName' and keep track of it. Since only module and object level scopes survive the compile phase, those are the only ones I worry about. To get the built-in types to work again (Name resolution can't find them) I add some 'fake' modules.


globalScope.addType(new Scope { thisType = "int", moduleIndex = 0 });
globalScope.addType(new Scope { thisType = "string", moduleIndex = 1 });
globalScope.addType(new Scope { thisType = "void", moduleIndex = 2 });
globalScope.addType(new Scope { thisType = "bool", moduleIndex = 3 });


I should be able to add some member functions to built in types using this same technique.

There's an unrelated problem with the language as is. I can't call the base implementation of virtual functions. For virtual functions to work, the derived type's implementation must replace the base implementation in the function table. So I stick the base implementation on the end of the function table, and the derived implementation entry keeps a reference to it.

I create a special syntax for calling the base implementation. It looks like a function called 'base' is being called, but it's actually a special node. Listing the base call rule before function calls keeps them from conflicting, though it does mean it's impossible to call a function named 'base'. A bit of special-casing allows constructors to call any base constructor.


public override void gatherInformation(ScopeStack context)
{
var parameterTypes = new String[ChildNodes.Count];
for (int i = 0; i < ChildNodes.Count; ++i)
parameterTypes = (ChildNodes as CompilableNode).getResultType(context);

var containingFunction = context.topScope.searchForFunction();
var decoratedName = ScriptEngine.decorateFunctionName(containingFunction.AsString, parameterTypes);

if (containingFunction.AsString == "construct")
{
if (containingFunction.decoratedName == decoratedName)
function = containingFunction.thisFunc.BaseImplementation;
else
{
function = context.topScope.findFunctionByDecoratedName(true, true, decoratedName);
if (function != null && function.BaseImplementation != null)
function = function.BaseImplementation;
}
}
else
function = containingFunction.thisFunc.BaseImplementation;

if (function == null) throw new CompileError("Function does not exist in base");
if (ChildNodes.Count != function.ParameterCount - 1) throw new CompileError("Wrong number of arguments to base");
if (decoratedName != function.DecoratedName) throw new CompileError("Argument type mismatch");

foreach (var child in ChildNodes)
(child as CompilableNode).gatherInformation(context);
}


It's about time I implemented arrays. I think those will be next. I'm not exactly sure how I'll implement them. I want them to be dynamically sized, so they will need to have member functions. Which means they'll need to be a type. But I want to be able to create arrays of anything. I'll either have to drop a load of special case code into type resolution or implement some sort of generic type.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement