Brass 3 (Under Development)

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

Moderators: benryves, kv83

User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

King Harold wrote:What would happen if you tried to name something "macro"?
The logic is this: if an argument is only one token long (eg "x", "value" or "macro") it is implicitly treated as a "value" argument. If the argument is two tokens long ("value x", "macro value", "macro macro") then the name is the second token and the type is the first token (and can either be "value" or "macro").
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I've added a number of bits and pieces; list file exporter plugins, number encoders, data structures and indexing.

For the second half of that statement, here's a real example:

Code: Select all

ComplexNumber: .data ticomplex -234.5, +0.25 ; -234.5+0.25i

/*

This outputs the data:

	8C 82 23 45 00 00 00 00 00
	0C 7F 25 00 00 00 00 00 00

To copy the components to OP1 and OP2 you could
do something like this:

	ld hl,ComplexNumber[1]
	rst rMov9ToOP1
	bcall(_OP1toOP2)
	
	ld hl,ComplexNumber[0]
	rst rMov9ToOP1

*/
Labels now have types associated with them, which gives them a size and so means that you can use an index (inside []) to access element addresses individually.

You can define new structures at runtime with .struct:

Code: Select all

.struct Vector3
	byte X,
	byte Y,
	byte Z

.struct Plane
	Vector3 Normal,
	byte Offset

.struct Edge
	Vector3[2] Ends

.varloc 0, 1024
.var Edge SomeEdge

    ld (SomeEdge[0].X),a

.var Plane SomePlane

    ld a,(SomePlane.Normal.X)
(I've also added a .var directive).
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

This is so totally insane... I love it! :)
if an argument is only one token long (eg "x", "value" or "macro") it is implicitly treated as a "value" argument. If the argument is two tokens long ("value x", "macro value", "macro macro") then the name is the second token and the type is the first token (and can either be "value" or "macro").
Is this really necessary? Can't it "detect" what it is you want? Because usually you'll want the macro style replace with something textual (label, string) and the value with something numeric or a known variable.

This is some seriously extreme preprocessing for a z80 assembler :) I just know people are going to write the most horrific things with this, as well as the most brilliant (and unreadable) things.
http://clap.timendus.com/ - The Calculator Link Alternative Protocol
http://api.timendus.com/ - Make your life easier, leave the coding to the API
http://vera.timendus.com/ - The calc lover's OS
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Timendus wrote:Is this really necessary? Can't it "detect" what it is you want? Because usually you'll want the macro style replace with something textual (label, string) and the value with something numeric or a known variable.
I don't really like attempting to guess what the user wants. Take the factorial example;

Code: Select all

#function f(n) 
   .if n == 0 
      f = 1 
   .else 
      f = n * f(n - 1) 
   .endif 
#endfunction 

val = 10
.echoln f(val)
In that instance you'd really want val to be evaluated and passed as the number "10", even though you're passing in a label. Of course you could force it with f(val+0), but then things start getting messy. :)

I agree that if you pass a string constant ("") it should default to passing-by-macro, so good idea. :)
This is some seriously extreme preprocessing for a z80 assembler :)
I'm trying to make it as flexible as possible before a release so that people can write useful extra directives and functions for the assembler. The examples are designed to be very silly and over the top to stress-test the parser and preprocessor. :)

Edit: I should also mention that thus far I haven't implemented the old Brass-style macros (eg .define ld_a({0}) xor a). I don't know how useful they would be, maybe something for the "legacy" plugin collection I'm arranging (see also: .equ).
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Hm, next "problem" I need some community guidance on.

The output as generated by the compiler is a large array of OutputData structure, which contains the page number, program counter and array of bytes output at that address (array of bytes thanks to output modifiers - unsquished programs have two bytes of output data per byte written by the compiler).

Now, currently, I sort by page then by program counter then write sequentially, which is clearly "wrong" (think of .relocated blocks).

My current idea is to maintain two variables - program counter and "output counter". The output counter increments with the program counter and is set by directives like .org, but if you write to the $ directly ($ = xyz) or use a directive like .relocate only $ gets modified. This way when sorted things end up (hopefully) in the right order.

Any thoughts?

Bear in mind that "holes" in the source code (ie, non-consecutive output addresses) can be present; in a raw file they'd be ignored, in a hex file each data record has an address anyway (so that's OK). Maybe I need a "padded raw" output writer that pads the data so it sits neatly on pages, and have a page definition directive?

I fixed the parser so indexers and field accessors work properly, so "ld a,(Var[2].Plane[3].Normal.X)" now compiles if you wanted something like that. I also fixed right-associative operators (order of precence is from right-to-left, rather than left-to-right, like x = y = 1).
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

Sounds like it should work, but I'm not really up to speed with all that... So don't take my advice :)
http://clap.timendus.com/ - The Calculator Link Alternative Protocol
http://api.timendus.com/ - Make your life easier, leave the coding to the API
http://vera.timendus.com/ - The calc lover's OS
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Well, it "seems" to work. :) I created a rawpages plugin that outputs the defined pages in their index order, writing X bytes per page where X is the defined size of the page.

As an example, Sega recommend the following SMS memory map for megabit cartridges. There are three "windows" that can be swapped in of 16KB each; they recommend that you use a fixed 32KB in the lower two windows and vary the third window's 16KB page (the final 16KB memory is mapped to RAM).

So, in Brass code;

Code: Select all

.defpage 0, $0000, kb(32)

; Page numbers are offset by one so that I can take the page
; of a label (:label) and output that to $FFFF directly to switch
; to the correct page.
.for p is 1 to 6
    .defpage p + 1, $8000, kb(16)
.loop

.page 0 ; Implicitly sets $ and output counter = 0
    xor a
    ld ($FFFC),a ; RAM control.

    ld ($FFFD),a ; Page 0 = 0
    inc a
    ld ($FFFE),a ; Page 1 = 1
    inc a
    ld ($FFFF),a ; Page 2 = 2

.page 2 ; Implicitly sets $ and output counter = $8000

    ; ...
(KB is a silly function that returns 1024xvalue).

I'm making some parts of the core plugin collection extendable; for example, the SMS requires certain data to be written into the ROM (checksum, TMR SEGA header and so on) as well as SDSC tags (author, description and so on). Thus, a "smsrom" plugin writes the stuff then can hijack the "rawpages" plugin to perform the actual writing to disk.
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

The help viewer can now export all of the documentation into a single website (including a contents frame). I'll make it zip up the output files so that it can be easily distributed/downloaded.

I've uploaded a copy here if you want to observe some of the documentation as it grows. :)

Brass 3 Plugin Documentation
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I wrote some output writer plugins for TI development; ti8x, ti83, ti82, ti85, ti86, ti73.

To take advantage of the better page support, each page is output as a different program variable.

Code: Select all

/* The following would create a group file
containing two programs, PRGMA and PRGMB. */

.page 1
.tivariablename "PRGMA"

.org $9D93
    ret

.page 2
.tivariablename "PRGMB"

.org $9D93
    ret

/* Naturally, you need to be using a TI program
output writing plugin for this to work. */
I have also promoted ":" to an operator in its own right (eg :y is seen as ":" and "y" rather than ":y") which makes macro replacement much easier.
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

To be entirely honest, I understand only about half the things you say, but what I understand of it sounds great :)
Why does that look so much better than my documentation? :mrgreen:
I think I'm going to steal parts of it from you ;)
http://clap.timendus.com/ - The Calculator Link Alternative Protocol
http://api.timendus.com/ - Make your life easier, leave the coding to the API
http://vera.timendus.com/ - The calc lover's OS
User avatar
driesguldolf
Extreme Poster
Posts: 395
Joined: Thu 17 May, 2007 4:49 pm
Location: $4080
Contact:

Post by driesguldolf »

Don't forget to release a "Hello World!" program! :D
(I never got the 'old' brass to properly compile a asm program, I kept using devpac8x for doing .bin->.8xp) :oops:

anyway, wouldn't it it better to make an .endpage directive?
benryves wrote:I have also promoted ":" to an operator in its own right (eg :y is seen as ":" and "y" rather than ":y") which makes macro replacement much easier.
that's a good idea ;)

What happens when u use .page n when you haven't yet defined that page?

And just to be sure, what output does this generate:

Code: Select all

  x=20
.for t is 0 to x
  .db t
  x=x-1
  t=t*2
.loop
EDIT:
is this valid?

Code: Select all

.module x
  y=255
.endmodule
.module x
  z=y
.endmodule
;Now should x.y=x.z=255
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

driesguldolf wrote:Don't forget to release a "Hello World!" program! :D
(I never got the 'old' brass to properly compile a asm program, I kept using devpac8x for doing .bin->.8xp) :oops:
Hm, putting .binarymode TI8X \ .variablename "PRGMNAME" should be enough to turn a .bin->.8xp.
anyway, wouldn't it it better to make an .endpage directive?
I'm not sure what advantage that would have..?
What happens when u use .page n when you haven't yet defined that page?
The page number will be set (:$ = <page>) and the output and program counters will be set to zero.

As for what will be output, it depends on the output writer. The raw writer will just spit out a binary of all pages, Intel HEX will also emit a correct object file, but the rawpages directive requires that all pages be defined so it knows how to format each page. In the case of undefined pages it displays a warning that it found data on page X, but page X wasn't defined so it doesn't appear in the output.
And just to be sure, what output does this generate:

Code: Select all

  x=20
.for t is 0 to x
  .db t
  x=x-1
  t=t*2
.loop
0, 1, 3, 7, 15.
EDIT:
is this valid?

Code: Select all

.module x
  y=255 ; <-
.endmodule
.module x
  z=y
.endmodule
;Now should x.y=x.z=255
Yes. :) The logic is that the current module name is appended to all newly created labels (so, on the line y=255 it sees "y, hmm, don't know what that is, so I'll create a new label called "x.y").

As for label resolution, the compiler starts by searching the current module for a match; if none is found it moves up a module and searches again, moving up until it reaches the "global" label scope.
Timendus wrote:Why does that look so much better than my documentation? :mrgreen:
I think I'm going to steal parts of it from you ;)
Probably because I got the MSDN team to design it for me. :pirate: (Sorry about the awful CSS/HTML, it was chucked together quickly).


---

The silly parser in Brass 1 would strip out all whitespace first of arguments (unless it was detected inside a string) and work on that.

A side-effect of that was that the following source would assemble:

Code: Select all

 ld h l, 1 23 ; Assembles as ld hl,123
This could actually be quite useful in some instances where you, say, wanted to pass a register pair to a function like this:

Code: Select all

.deflong some_function(regh, regl)
	inc regh regl
.enddeflong

some_function(h,l)
Brass 3 has a much stricter (and more complex) parser, so the above simply wouldn't work. This results in a missing feature, which we can't have..!

I'm trying to avoid writing parser hacks. Another parser hack in Brass 1 was the replacement of [%var%] with the environment variable "var". As a compatibility hack I wrote a plugin that registers a bunch of macros to replace "[%var%]" with the contents of var (as [%var%] is five tokens - [, %, var, %, and ] - I can only replace the single token "[%var%]") but this doesn't help in the situations where you need to pass in a numeric value. To get around this I cobbled together an eval() function that evaluates whatever is in the argument and returns a numeric value. Thus;

Code: Select all

; Brass 1 code:
.db "[%project_name%]"       ; debug_name is a string.
DebugLevel = [%debug_level%] ; debug_level is a number.

; Brass 3 code:
.db "[%project_name%]"               ; no change required :)
DebugLevel = eval("[%debug_level%]") ; evaluate to convert string->number.
How does this help with the above regh/regl feature? Well, why not beat around the bush and make labels capable of handling string values or numbers? Proper string handling helps in a lot of places, after all. Thus;

Code: Select all

.function some_function(regh, regl)
	eval("inc " + regh + regl)
.endfunction

some_function("h", "l")
Some of the operators have been modified to change their behaviour if either of the arguments are passed as strings. + or & will concatenate strings if either operand is a string (as will their assignment versions += and &=); = assigns strings; ==/!=/>/>=/</<= compare string values. If either value is passed as a string then the string operator takes over; you can force conversion to a number by multiplying by 1 (returns the current encoding version) or by using eval() (parses as a number). Thus;

Code: Select all

x = "1"
y = x       ; y = string "1".
y = x + 0   ; y = string "10".
y = x * 1   ; y = number 49 (ASCII '1').
y = eval(x) ; y = number 1.
To complement the new string support are a bunch of string manipulation functions.

Code: Select all

.function rev(s)
    rev = ""
    .for i = 1, i <= strlength(s), ++i
        rev += strsub(s, -i, 1)
    .loop
.endfunction

; Displays "Hello!"
.echoln rev("!olleH")
I've also thrown together some Brass 1 compatible directives - from the simple .asc (just registers a macro that replaces .asc with .db) to a string encoder that mimics the behaviour of .asciimap.

Latenite 1 compatibility is going to be fairly important. Brass 3 is quite different, but has been developed in such a way that for the most part it will integrate with Latenite 1 as well as Brass 1 currently does.

The current compilation process is to set a bunch of environment variables then invoke a compile command file (.cmd), which in turn runs Brass with a shedload of command-line arguments.

Brass 3 just takes one command-line argument - a project filename. Thus, the workaround is to do this;
  • Delete/move/rename existing Brass.exe in "Compile" directory.
  • Create a "Brass3" directory inside "Compile".
  • Copy new Brass.exe, plugins to Compile\Brass3
  • Copy CreateBrassProj.exe and CreateBrassProj.proj to Compile\Brass3.
  • Create Compile\Brass.cmd that invokes CreateBrassProj.exe and Brass.exe.

Code: Select all

"%COMPILE_DIR%\Brass3\CreateBrassProj.exe" Latenite.brassproj %1 %2 %3 %4 %5 %6 %7 %8 %9
"%COMPILE_DIR%\Brass3\Brass.exe" Latenite.brassproj > Nul
CreateBrassProj is a combination of a Brass 3 project file and Brass 1 command-line argument processor. It loads its project file, parses the command-line arguments then spits out a new project file that causes Brass 3 to simulate most of what Brass 1 would have done (including loading list file generators to emit the Errors.xml and Debug.xml, which are part of the "Legacy.dll" plugin collection).

In brief; all you will hopefully have to do to get Brass 3 support in Latenite 1 is to copy a few files over and delete the old Brass.exe.

To further enhance compatibility, the Help viewer can output Latenite help files (.xml), resulting in highlighted Brass 3 directives and functions.

Image
Click for big.


For some more examples ("further reading"): eval(), year(), freadtext(), strtoken(), envvars, .asciimap, .clearpage.
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

benryves wrote:
Timendus wrote:Why does that look so much better than my documentation? :mrgreen:
I think I'm going to steal parts of it from you ;)
Probably because I got the MSDN team to design it for me. :pirate: (Sorry about the awful CSS/HTML, it was chucked together quickly).
*Steals* ;)
http://timendus.student.utwente.nl/~vera/asmdoc/
(See test.asm, others are pretty much empty)

The HTML/CSS really is a mess.. So I just copied some of the colors and a bit of css here and there, tried to make it look professional but not an exact copy. I'm not sure about the extendable tree. It's not necessary for now, and probably not worth my time to redo that at the moment. So maybe later.

It should have syntax highlighting though. Any ideas on how we could do that with XSL..? :mrgreen:

(Sorry for spamming in your topic, by the way ;) The string functions and Latenite integration look really cool, almost makes me regret not using Windows anymore :))
http://clap.timendus.com/ - The Calculator Link Alternative Protocol
http://api.timendus.com/ - Make your life easier, leave the coding to the API
http://vera.timendus.com/ - The calc lover's OS
User avatar
benryves
Maxcoderz Staff
Posts: 3080
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Cool stuff, but renders very strangely in Opera 9. :|

Latenite 1 compatibility is just a temporary hack, ultimately I'd like to build a .NET-based IDE (Latenite 1 uses a lot of Win32 code). Hopefully Mono's WinForms will be up to the task. :)
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

benryves wrote:Cool stuff, but renders very strangely in Opera 9. :|
That is weird... I'll look into it. Hadn't tested in Opera yet.
(Actually, I've only got Firefox here and I'm too lazy to install anything else when I've got my Grande Browser Compatibility Testsuite all set up at home ;))
Latenite 1 compatibility is just a temporary hack, ultimately I'd like to build a .NET-based IDE (Latenite 1 uses a lot of Win32 code). Hopefully Mono's WinForms will be up to the task. :)
Whiiiiiiee :mrgreen:
Can't want to transparently wobble Latenite in my Beryl/Compiz! :mrgreen:
http://clap.timendus.com/ - The Calculator Link Alternative Protocol
http://api.timendus.com/ - Make your life easier, leave the coding to the API
http://vera.timendus.com/ - The calc lover's OS
Post Reply