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: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I've been comparing binaries output by old Brass and this new assembler (pixelmad.z80, eliza.z80, CoBB's pinball.asm, kv's slip.z80, Joe P's asteroid.z80), and finally(!) the two are matching (the usual errors of reading ld de,(xyz)+1 as ld de,(*) and so on seem to have been fixed).

There's a large amount of error trapping to add (for example, bcall(xyz) where bcall hasn't been defined creates a new label, bcall(xyz), rather than raising an error), more testing and the addition of the many missing features (.defcont, .deflong, .var, structs, reusable labels, ...) that I haven't got around to yet.

I hope to get a demo out soon for testing :D
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Various parser updates and fixes have been implemented (including reintroduced support for the ternary conditional operator ?: ).

Slightly strangely, ?: has very low operator precedence. This means that in the following expression:

Code: Select all

x = 0
y = true() ? ++x : --x
y is 0, not 1. The reason for this is that as ?: has low precedence, it gets evaluated after both ++x and --x.

To further confuse issues, before adding ternary operator support to the parser I implemented an if() function, like this:

Code: Select all

x = 0
y = if(true(), ++x, --x)
This was inspired by VB's IIf() function. However, as this function can pick and choose which expressions to evaluate, y becomes 1. This is backwards to convention where the C-style ?: operator is lazy (only executing the successful side) and the BASIC-style if() isn't, executing both sides first.

Multi-line statements are the next area of contention. Currently a statement is terminated with a newline or a \ character. However, in some special cases, this termination is disabled - for example, by the .define directive. Another directive that does this is the .enum directive, which results in syntax like this:

Code: Select all

.enum Days
	Sunday,
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday
\

/*
This creates the following labels:
Days.Sunday   = 0
Days.Monday   = 1
Days.Tuesday  = 2
...
Days.Saturday = 6
*/

.enum Numbers
	Eleven = 11,
	Twelve,
	Thirteen,
	Twenty = 20,
	Vingt = 20,
	Zwanzig = 20,
	TwentyOne,
	Four = 4,
	Five
\

.echoln Numbers.Eleven    ; 11
.echoln Numbers.Twelve    ; 12
.echoln Numbers.Thirteen  ; 13
.echoln Numbers.Twenty    ; 20
.echoln Numbers.Vingt     ; 20
.echoln Numbers.Zwanzig   ; 20
.echoln Numbers.TwentyOne ; 21
.echoln Numbers.Four      ; 4
.echoln Numbers.Five      ; 5
I think that using the \ termination character is rather neat. Others may disagree, so I'd like to hear your ideas. :)

In a similar vein, I've added support for runtime-aliased plugins. This can be used by a user-defined function plugin, so the following is now possible:

Code: Select all

; A recursive function that calculates n!
.function factorial(n)
	if(n <= 0, 1, n * factorial(n - 1))
\

; 1.5511210043331E+25
.echoln factorial(25)
Finally, for silliness, I've added a new plugin collection that handles images:

Code: Select all

; Open the image:
img = imgopen("delete.png")

; Loop over each pixel in the image:
.for y = 0, y < imgheight(img), ++y
	.for x = 0, x < imgwidth(img), ++x
	
		; Grab the luminosity of each pixel to two bits:
		l = imggetpixel(img, x, y, 'l2')
		
		; Convert to an 'ASCII-art' brightness:
		c = choose(l + 1, ' ', '.', '+', '#')
		.echochar c, c
		
	.loop
	.echoln
.loop

/*
          ....++++....
        ++++########++..
      ++######++++++##++..
    ++++##++++++++++++##++..
  ..++##++++++++++++++++##..
  ..##++++++++++++++++++++++..
  ++##++++############++++++..
  ++##++++############++++++..
  ..++++++++++++++++++++++++..
  ....##++++++++++++++++++..
    ..++++++++++++++++++++..
      ..++++++++++++++++..
        ....++++++++....
            ........
*/
"l2" in imggetpixel is the pixel format you want - for example, "argb" or "b2g2r2" (if no width is specified, 8 bits is assumed).

It might look useless, but it at least gives me some decent parser testing material. :)
Last edited by benryves on Wed 03 Oct, 2007 11:55 am, edited 1 time in total.
User avatar
Timendus
Calc King
Posts: 1729
Joined: Sun 23 Jan, 2005 12:37 am
Location: Netherlands
Contact:

Post by Timendus »

Dude, seriously :) What's next? A can opener? :)

I think the backslash is a bit confusing, being the Unix minded guy that I am, as it usually represents that the line continues beyond the \n, not that it ends...

You'd expect:

Code: Select all

.define bcall(xyz) \
      rst 28h \
      .dw xyz
not

Code: Select all

.define bcall(xyz)
      rst 28h
      .dw xyz
\
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: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

The parser sees both \ and the carriage return as statement terminators; it needs to see \ as a statement terminator (rather than line continuation) for TASM's sake (hence the bcall macro expanding to "rst 28h \ .dw label").

Requiring an explicit end-of-statement character (like C's ';') isn't going to work (would break compatibility with all other source files, assembly is line based). We rather need to keep \ as a terminator, but one could introduce a line continuation character like BASIC's _:

Code: Select all

.define bcall(xyz) _
      rst 28h _
      .dw xyz
Then the enum would have to look like:

Code: Select all

.enum Days 
   Sunday, _
   Monday,  _
   Tuesday, _
   Wednesday, _
   Thursday, _
   Friday, _
   Saturday
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

Or you could use a completely different character as 'closing char', } ] | > or something, or..
.endenum
.enddefine
but that's old-style..
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Yes, I was originally going to use .end*, but that requires more stuff hacked into the parser to catch that sort if thing.

It's appealing in the sense that other directives, such as the repetition ones (.for/.loop/.while or .if) require a terminating directive (.loop or .endif).

The choices are there, this is why I'm asking you lot what you think I should go for :D
User avatar
driesguldolf
Extreme Poster
Posts: 395
Joined: Thu 17 May, 2007 4:49 pm
Location: $4080
Contact:

Post by driesguldolf »

how about:

Code: Select all

.enum days
   Monday, Tuesday, Wednesday,    ; Though comments should be allowed here
   Thursday, Friday,
   Saturday, Sunday
; Done because there is no ","
basically if a "," is appended then there is a next statement (eventually on the next line)
Nice and clean ^^

This can be generalised:

Code: Select all

.db 0,0,0,0,
0,0,0
This would then be valid, or (if you don't want this) you could give these kind of directives a flag:
true=allows statements to continue on the next line
false=the opposite of true

And all TASM source files will compile properly under the new Brass



Though this approach does not work with .define :(
and I don't see a way (other than \ and .defcont) to handle this
.function shouldn't be a problem for backward compatibility (because it doesn't exists in TASM :P)

Code: Select all

.function test (par1, par2) {/*code goes here*/ xor a\ ld b, a\ ld c, a
   ld h, par1
   ld l, par2
; Note that I have no idea how the function system works
; thus the last 2 lines can be completely illegal :P
}
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

I do rather like that idea (if last non-whitespace/non-comment token is a comma = ignore newlines).

Functions currently only allow mathematical expressions (so not even directives work in them) which needs to be fixed anyway; I'd rather use a .function/.endfunction pair and let the user put anything they like inside them. I'll need to add a public method to the compiler that will let a plugin author "inject" some code.
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

Good idea! it looks really natural so that is good


I'll remember this forever :)
false=the opposite of true
User avatar
driesguldolf
Extreme Poster
Posts: 395
Joined: Thu 17 May, 2007 4:49 pm
Location: $4080
Contact:

Post by driesguldolf »

King Harold wrote:I'll remember this forever
Dries wrote:false=the opposite of true
XD that is one to remember, the most hilarious things happen when you least expect it :mrgreen:

One little thing though:
Do you have to start the enumeration on the next line you write .enum? maybe you can have one big list and take the first statement as the name. This allows more flexibility:

Code: Select all

.enum name,
 one, two, three,
 test1, test2, test3

Code: Select all

.enum name, one, two, three
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Code: Select all

.enum name item1, item2, item3
...is also valid. The first item is the name, it's just the rest of the line that needs to be comma-delimited.

I implemented your comma trick and it works very well, so thanks for the idea. :)

Functions now work "properly" (and in about a third of the lines of the original function implementation!) The compiler pulls in source code, tokenises and caches it (after matching assembly source code and directives) in a big list. You can recall the current statement and tell the compiler to jump to a particular statement (by index), which is how the looping directives work.

The function plugin, then, simply has to remember the current position +1 (entry point), switch off the compiler (which means that nothing is executed until a matching directive is hit, in this case .endfunction), remember the current position -1 (exit point). When the function is invoked it just asks "recompile statements between entry point and exit point". :)

A few sample snippets:

Code: Select all

/* Output the eight bits of value as 1s or 0s */
.function echobinary(value)
	.for bit is 7 to 0
		.if value & 1 << bit
			.echo 1
		.else
			.echo 0
		.endif
	.loop
.endfunction

/* Output the eight bits of value as 1s or 0s with a % prefix */
.function echobinarybyte(value)
	.echo '%' \ echobinary(value)
.endfunction

/* Output the sixteen bits of value as 1s or 0s with a % prefix */
.function echobinaryword(value)
	.echo '%'
	echobinary(value >> 8)
	echobinary(value)
.endfunction

/* Output the four bits of value as a hex digit */
.function echohexnybble(value)
	.echochar choose(1 + (value & %1111),
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
.endfunction

/* Output the eight bits of value as two hex digits */
.function echohex(value)
	echohexnybble(value >> 4)
	echohexnybble(value)
.endfunction

/* Output the eight bits of value as two hex digits */
.function echohexbyte(value)
	.echo '$' \ echohex(value)
.endfunction

/* Output the eight bits of value as two hex digits */
.function echohexword(value)
	.echo '$'
	echohex(value >> 8)
	echohex(value)
.endfunction

echobinaryword(1234) ; Outputs %0000010011010010
.echoln

echohexword(%1011111011101111) ; Outputs $BEEF
.echoln

Code: Select all

/* Enumeration for different calculator models */
#enum Model
	TI8X, ; TI-83 Plus
	TI83  ; TI-83

/* Set to TI8X or TI83 */
Model = Model.TI83

/* Use bcall() to invoke ROM calls */
#function bcall(label)
	.if Model == Model.TI8X
		rst 28h
		.dw label
	.else
		call label
	.endif
#endfunction

Code: Select all

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

.echoln f(30) ; Outputs 2.65252859812191E+32.

Code: Select all

.function distance(x,y)
	distance = sqrt(x * x + y * y)
.endfunction

.echoln distance(3, 4) ; Outputs 5.
When called, the arguments are evaluated then passed (as local labels in a new, temporary module) into the function. This has some disadvantages; such as passing in a label that may or may not be declared (for example):

Code: Select all

.function lda(value)
    .if defined(value) && value == 0
        xor a
    .else
        ld a,value
    .endif
.endfunction

lda(fowardreference) ; Error

forwardreference = 100
The easiest way to implement this would be to use constraints on function declarations:

Code: Select all

.function lda(value) where defined(value) && value ==0
        xor a
.endfunction

.function lda(value)
    ld a,value
.endfunction
Where you have multiple declarations for functions you'd pick the most specific one that matches. Another possibility would be to use macro substitution with arguments rather than evaluate them:

Code: Select all

.function lda(macro value)
        ; ...
.endfunction
The macro idea has the added feature that you can use it to pass-by-reference and also pass string literals and suchlike. Both have their advantages, so I might go for both, but - as always - I'd like to know your opinions.
User avatar
driesguldolf
Extreme Poster
Posts: 395
Joined: Thu 17 May, 2007 4:49 pm
Location: $4080
Contact:

Post by driesguldolf »

benryves wrote:I implemented your comma trick and it works very well, so thanks for the idea. :)
No problem at all :D

benryves wrote:The function plugin, then, simply has to remember the current position +1 (entry point), switch off the compiler (which means that nothing is executed until a matching directive is hit, in this case .endfunction), remember the current position -1 (exit point). When the function is invoked it just asks "recompile statements between entry point and exit point".
I got nothing to add :P

benryves wrote:

Code: Select all

/* Output the eight bits of value as 1s or 0s */
.function echobinary(value)
   .for bit is 7 to 0
      .if value & 1 << bit
         .echo 1
      .else
         .echo 0
      .endif
   .loop
.endfunction
as long as in ".for bit is 7 to 0" (there's no need to be able to modify 7 in the loop) 0 can be modified in the loop itself, like:

Code: Select all

value=5
.for par is 0 to value
  .db value
  value=value-1
.loop
This should output:

Code: Select all

.db 5,4,3,2 ;I suppose it terminates when par>value (and not >=)

Ah, you can still choose between "." and "#" for directive prefixes


Some notes:
why do you use textual delimiters instead of commas? (no idea if that sentence makes sense ^^)
this is feels better imo:

Code: Select all

;(1)
.for bit=7, bit<=0, bit=bit-1
.loop

;(2)
.for bit,7,0
.loop
the only problem with (1) is that some mastermind may write a condition that is never reached :mrgreen:, though I still prefer (2) over the textual delimiters and (1) over (2)

benryves wrote:When called, the arguments are evaluated then passed (as local labels in a new, temporary module) into the function.
...
...
...
Both have their advantages, so I might go for both, but - as always - I'd like to know your opinions.
I don't understand this part...
parameters of a function shouldn't be labels (as labels can only store numbers), rather some textual representations of some content. though how will you make the difference between labels and parameters...
I'm confused about it...

Anyway I am glad I can help you make Brass even better (and it's looking so good already!!!) :D
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

Two different for loops are supplied:

Code: Select all

; "C-style"
.for <initialisation>, <condition>, <increment>
.loop

; "BASIC-style"
.for <label> is <start> to <end> [step <increment>]
.loop
The use of commas vs text is just to differentate between the two more easily.
...shouldn't be labels (as labels can only store numbers), rather some textual representations of some content. though how will you make the difference between labels and parameters...
This is why I suggested the use of a "macro" argument, so:

Code: Select all

.function repeatstring(macro x, repeat)
    .for i=0, i < repeat, ++i
        .db x
    .loop
.endfunction

repeatstring("Hello", 3) ; Outputs "HelloHelloHello"
"x" is not evaluated and gets macro-replaced into the ".db" statement, but "repeat" gets evaluated as a number.
User avatar
driesguldolf
Extreme Poster
Posts: 395
Joined: Thu 17 May, 2007 4:49 pm
Location: $4080
Contact:

Post by driesguldolf »

Just PERFECT! :D
benryves wrote:"x" is not evaluated and gets macro-replaced into the ".db" statement, but "repeat" gets evaluated as a number.
yes you will need to support them both:
you can just find/replace macro arguments, but you can't do that with the numbers because they can be changed during the function (macro arguments cannot)

Ok that isn't very good English... :P
All I want to say is find/replace macro arguments and always re-evaluate labels

Which is just as you said :P
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

What would happen if you tried to name something "macro"?
Post Reply