Brass 3 takes some of Brass 2's ideas - ie, a user-extendable plugin-driven compiler - yet simplifies it quite a lot. The Brass 2 parser was pretty sophisticated, which meant that whilst it was based on very clean code trying to shoe-horn in additional functionality like a macro preprocessor (which modified the source) or various odd bits of syntax (like TASM's '.equ') was a nightmare and just didn't work.
The new parser is still light-years ahead of the original one in Brass (including much better operator support and functions), though!
There's a vast amount of stuff that needs to be implemented (modules, reusable labels, lots of missing directives) but at least the concept has stood up well thus far.

I've gone attribute-crazy (attributes are used in .NET to attach metadata to types/functions/&c) and so, for example, individual plugins can expose their documentation. From this you can get a nifty help viewer (that can be embedded into Latenite):
Of course, command-line apps don't usually look very interesting, but I couldn't resist syntax-highlighting error reports (the compiler knows what each token is - a label, an operator, an instruction, a comment, a directive - so syntax highlighting is effectively built-in):

I post this to hopefully commit myself to the project, and also driesguldolf seems to want .while \ .loop functionality so at least this indicates that it's coming.

As far as plugins go, here's the source for the .incbin plugin:
Code: Select all
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;
using Brass3;
using Brass3.Plugins;
using Brass3.Attributes;
namespace Core.Directives {
[Syntax(".incbin \"file\"")]
[Description("Insert all data from a binary file into the output at the current program counter position.")]
[Remarks("Use this to import precompiled resources from other sources into your project.")]
[CodeExample("MonsterSprite:\r\n.incbin \"Resources/Sprites/Monster.spr\"")]
[Category("Data")]
public class IncBin : IDirective {
public string[] Names { get { return new string[] { "incbin" }; } }
public string Name { get { return Names[0]; } }
public void Invoke(Compiler compiler, TokenisedSource source, int index, string directive) {
int[] Args = source.GetCommaDelimitedArguments(index + 1, 1);
if (!source.ExpressionIsStringConstant(Args[0])) throw new DirectiveArgumentException(source, "Filename expected.");
string Filename = compiler.ResolveFilename(source.GetExpressionStringConstant(Args[0], false));
if (!File.Exists(Filename)) throw new DirectiveArgumentException(source.Tokens[index], "File '" + Filename + "' not found.");
try {
using (FileStream FS = File.OpenRead(Filename)) {
switch (compiler.CurrentPass) {
case AssemblyPass.Pass1:
compiler.Labels.ProgramCounter.Value += FS.Length;
break;
case AssemblyPass.Pass2:
int Data = 0;
while ((Data = FS.ReadByte()) != -1) compiler.WriteOutput((byte)Data);
break;
}
}
} catch (Exception ex) {
throw new CompilerExpection(source, ex.Message);
}
}
}
}
You should be able to write plugins in any CLI-targetting (".NET") language. Another problem with Brass 2 was that plugins were static. Brass 3 creates instances of plugins, and provides an easy way for them to access eachother and thus share data between them by allowing them to query the compiler for other directives (eg, the fclose() function can ask for the instance of the fopen() function plugin from the compiler so it can access fopen()'s file handle table).