(This post is partially to indicate progress, and partially to post some sample code from the plugin system).
I decided to add functions. They're not that tricky, really... if a constant token is found (a constant being something like 'abcd' or '"fish"' or 763) with an opening parenthesis directly after it, all the stuff between that parenthesis and the matching closing one is removed from the expression and dumped 'inside' the constant token, which is turned into a new 'function' token. When the token is executed, rather than return the label or numeric value associated with it, it's name is looked up from the loaded plugins, and if one matches the arguments that were extracted are sent to the plugin.
I've wrapped up the majority of .NET's Math class as plugins. The Core Plugins package comes with a few classes of its own that can be use to aid plugin development (such as ways to hook in to its conditional compilation system, and in this case a specialised template that accepts a fixed number of comma-delimited arguments, called CommaDelimitedFunction where the third argument added to the constructor is the number of arguments required).
Code:
using System;
using System.Reflection;
using Brass2;
using Brass2.Plugins;
using Brass2.Compiler;
namespace MixedCore {
public class Min : FunctionHelper.CommaDelimitedFunction {
public Min(Assembly containingAssembly, Builder compiler)
: base(containingAssembly, compiler, 2) {
}
public override string Identifier {
get { return "min"; }
}
protected override double EasyExecute(Builder.ExpressionGroup[] arguments) {
return Math.Min(arguments[0].ValueDouble, arguments[1].ValueDouble);
}
}
public class Max : FunctionHelper.CommaDelimitedFunction {
public Max(Assembly containingAssembly, Builder compiler)
: base(containingAssembly, compiler, 2) {
}
public override string Identifier {
get { return "max"; }
}
protected override double EasyExecute(Builder.ExpressionGroup[] arguments) {
return Math.Max(arguments[0].ValueDouble, arguments[1].ValueDouble);
}
}
}
All plugins need the Identifier, so Brass knows when to use it. EasyExecute in this case takes over from the primitive 'Execute' that's exposed by Brass to automatically handle incorrect numbers of arguments and to strip out the commas.
Why do I feel it worth supporting functions? Well, there's the old mess of the trig table generation functions (and remembering argument orders), so you have finer control over them with the above...
Code:
theta = -1
.rept 256
.db floor(127 * sin(++theta * pi() / 128))
.loop
(I haven't written a for-loop directive yet).
All plugins have a reference to 'Builder compiler'. This is the current compiler, so when called they can control the compiler in some way. In other words, if I was feeling very lazy, and didn't add macro support...
Code:
public class BCall : Function {
public BCall(Assembly containingAssembly, Builder compiler)
: base(containingAssembly, compiler) { }
public override string Identifier { get { return "bcall"; } }
public override double Execute(Builder.ExpressionGroup[] arguments) {
if (Compiler.IsWritingOutput) {
if (arguments.Length != 1) {
throw new Exception("BCALL expects one argument.");
} else {
ushort RomCallIndex = (ushort)arguments[0].ValueLong;
Compiler.WriteData(0xEF); // RST $28
Compiler.WriteData(new byte[] { (byte)RomCallIndex, (byte)(RomCallIndex >> 8) });
}
}
Compiler.CurrentInstructionPointer.Value += 3;
return double.NaN;
}
}
Sadly this won't work as you can't have floating expressions that don't perform an assignment.
