Brass 2: Dropping TASM backwards compatibility.

One suite to code them all. An complete IDE and assembler for all your z80 projects!

Moderators: benryves, kv83

User avatar
silver calc
New Member
Posts: 73
Joined: Tue 28 Mar, 2006 10:50 pm
Location: Wouldn't you like to know?

Post by silver calc »

Where do I put the 0104 key?
Please "encourage" me to work more on Image any way you deem necessary
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

you dont, it defaults to 0104
or if you mean the file.. I put it in the map "compile", not sure whether it should be there though
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Brass Website wrote:Setting up the SDK

Brass does not sign applications itself - it delegates the job to the Wappsign utility supplied as part of the TI-83 Plus SDK. You must have the SDK installed, therefore, to sign applications.

You need to run Wappsign. It should be listed somewhere in the TI-83 Plus Flash Debugger start menu entry, or in the \Utils subdirectory of the Flash Debugger's installation folder. For signing to work, the Wappsign application needs to know where your key file resides. Click the [...] button next to the 'Key File' box, then browse for the 0104.key file. Click 'Yes' on the 'This directory is not in your search path. Add it now?' dialog box. Tick the 'Save Settings on Exit' box, then click 'Close'.


Emphasis mine. Brass doesn't need to know about the 0104.key file - Wappsign does.

As Brass 2 can load custom output plugins, somebody clever might be able to write a self-signing application output plugin. For the moment, using Wappsign's COM interface is the way it's done.
User avatar
silver calc
New Member
Posts: 73
Joined: Tue 28 Mar, 2006 10:50 pm
Location: Wouldn't you like to know?

Post by silver calc »

Code: Select all

.binarymode ti8xapp                 ; TI-83+ Application

When I try this, it keeps giving me 'error: can't locate 0104 key'. I not sure, but is this an unfinished feature of Brass1? If so, would this work:

Code: Select all

.binarymode intelhex                 ; hex file
(and then use Wappsign)?
Please "encourage" me to work more on Image any way you deem necessary
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Sorry, I didn't see this post until now... did you ever get it working? :|

Good grief. I can see what Spencer was getting at when he went to write his own assembler. This is really rather embarrassing.

Code: Select all

.rept 9000
   ld a,1
.unsquish
   ld a,2
.squish
   ret
.loop


Pretty simple stuff. Rather meaningless, but there you go. Here's the result of oldschool Brass:

Code: Select all

Brass Z80 Assembler 1.0.4.9 - Ben Ryves 2005-2006
-------------------------------------------------
Assembling...
Pass 1 complete. (2093ms).
Pass 2 complete. (22062ms).
Writing output file...
Errors: 0, Warnings: 0.
Done!


You're not reading that wrong. Nearly half a minute to assemble that code. Keep that in mind. ;) Also keep in mind that that's a simple, single-assembly project built in the optimised release mode.

I've had a look back into Brass again. I've written quite a lot of code for it, and it's not doing an awful lot thus far. However, it can build the above code and produce identical output -

Code: Select all

Brass Assembler - Copyright © Bee Development 2005-2007
-------------------------------------------------------
ZiLOG Z80 - Copyright © Bee Development 2005-2006
TI Program Files - Copyright © Bee Development 2005-2006
Core Plugins - Copyright © Bee Development 2005-2006

Parsing source...
Building...
Writing output...
Time taken: 484.38ms.
Done!
That's almost 50 times faster; running in Debug mode, within the VS IDE, loading three other (also Debug) DLLs as plugins. I'm so, so, sorry. :(

Anyhow. So far, I have these directives in the 'Core Plugins' collection -

  • .if/.elseif/.else/.endif - conditional compilation.
  • .org/.porg - set the origin (.org) or pad up to the origin (.porg).
  • .align - align to a particular boundary (pads).
  • .rept/.loop - usual loop-unrolling stuff.
  • .push/.pop - pushing and popping a value onto a stack.
  • .db/.dw - the usual byte definition stuff.


Core Plugins also provides a 'Raw' output and an 'IntelHex' output plugin.

'TI Program Files' contains a series of .8?p output plugins, an 'Unsquish' byte transformer plugin and two directive plugins (.unsquish and .squish) that switch the byte transformer plugin on and off. (A byte transformer plugin takes a byte input and returns an array of bytes).

Do people have any particular favourite directives they use that I haven't listed above? (I know it's a rather incomplete list).
User avatar
kv83
Maxcoderz Staff
Posts: 2735
Joined: Wed 15 Dec, 2004 7:26 pm
Location: The Hague, Netherlands
Contact:

Post by kv83 »

to be honest, that's all I ever used :P
Image
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

kv83 wrote:to be honest, that's all I ever used :P
Well, include files usually have a ".equ". I might put .equ into a 'TASM' plugin collection, as for most people using the '=' operator should be good enough.

There's also TASM-style #define. I'm puzzling over how best to do this. Do I build function support into the emulator (so calling a function can output code and return a value?) or use a TASM-style text-replacement?
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

(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: Select all

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: Select all

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: Select all

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. ;)
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I've updated a fixed a number of the directives, and added a file operations plugin.

Code: Select all

fhnd = fopen("test.txt", r)

#while !feof(fhnd)
    chr = freadbyte(fhnd)
    .if chr >= 'a' && data <= 'z'
        #db chr + 'A' - 'a'
    .else
        #db chr
    .endif
#loop

fclose(fhnd)


I've added while and for loops too.

Code: Select all

#for theta = 0, theta < 360, ++theta
    .db min(127, round(128 * sin(deg2rad(theta))))
#loop


Alternatively, that could be written as:

Code: Select all

#for theta is 0 to 359
    .db min(127, round(128 * sin(deg2rad(theta))))
#loop


The advantage of the Basic-styled loops is that they have a higher chance of success when detecting infinite loops, and also don't have floating-point creep when using non-integer steps.

There's also the ability to declare your own functions at runtime, though I haven't figured out a clean way to return a value. For the moment they can be used like TASM's macros;

Code: Select all

_PutS = $450A

.function bcall(label)
    rst $28
    .dw label
.endfunction

bcall(_PutS)
User avatar
silver calc
New Member
Posts: 73
Joined: Tue 28 Mar, 2006 10:50 pm
Location: Wouldn't you like to know?

Post by silver calc »

This looks great... When are you going to release Brass2/LateNite2 for public use?
Please "encourage" me to work more on Image any way you deem necessary
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

There are a lot of things to sort out - least of all the Z80 assembler plugin isn't even finished - so "when it's done".

I realise it's rude to post previews but not deliver, but I wanted to demonstrate that this complete rewrite is worth it in at least it's a more robust system.

The biggest worry with releasing it is if someone decides to start writing plugins and I revise the way they're handled. :)

For starters, the current way a source file is represented (as a LinkedList<Command>) is not great as the functionality for compiling the list is built in to it - so you can't have two such lists of commands and build them into a single output, so this is where the returning values from a function problem comes in (it would be easier if I could split the function body into its own list of commands and execute that whenever the function is called) or even support include files!
User avatar
benryves
Maxcoderz Staff
Posts: 3074
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I've broken down the compiler into an "Overseer" (which still contains all of the methods for controlling the assembly process, such as switching the assembler on/off or jumping around the source using bookmarks) and "Source Segments" (which are a series of commands).

A source segment is typically a single assembly file, but by using two new methods - StartRecording and StopRecording - you can extract a block of code from the main assembly process and run it at a later date. You can run it alongside the main body of code (in the case of an include file) or externally (in the case of a function). Now, this sort of thing is possible:

Code: Select all

.function slow_mul(op1, op2)
    slow_mul = 0
    .rept abs(op1)
        .if sign(op1) > 0
            slow_mul += op2
        .else
            slow_mul -= op2
        .endif
    .loop
.endfunction

.echo slow_mul(log(100, 10), slow_mul(5, 4))


(I'm using the VB-style return mechanism where the return value is set by assigning to a variable with the same name of the function).

Something's not quite right with running the functions away from the main body of code, and calling another user-defined function from your own doesn't work, and so there's no recursion either. It should hopefully be a simple enough fix. A 'return' statement would also be easy enough; when encountered set the value as required and switch the assembler off for the remainder of the function. :)

I think the problem is the large number of static variables that still exist from the "one source segment only" idea. It should hopefully be easy enough to fix.
Post Reply