After encountering numerous bugs in spasm and simply not being able to fix them all, I found out that the creator of flat assembler (fasm), an x86 assembler written in x86 assembly, started a new project called flat assembler g (fasmg), which is a generic assembler written in x86 assembly.
Many useful commands useful for writing assembly-like programs are builtin to fasmg, except for the target specific instructions and directives themselves. These can be implemented with macros, for example a nop macro might look like:
macro nop
    db 0
end macro

Which causes any line of code consisting of nop to get assembled to a single zero byte.

I have implemented which defines macros for all the ez80 instructions and some ez80 specific directives, which allows outputting files in 8xp and similar formats, and, a wrapper around spasm's You can get the latest version of these files from GitHub, and you can get fasmg near the bottom of this page.

The main advantage of fasmg over spasm is a plethora of extra features, such as virtual output areas, loop commands, and forward-referenceable variables. The main disadvantage is that fasmg is an order of magnitude slower than spasm at parsing instructions due to each one having to go through an interpreted macro, for example my test involving all 4296 ez80 instructions takes .1 seconds to assemble with fasmg, but .01 seconds to assemble with spasm. Also, fasmg does not support listing out-of-the-box.

You can look at the hello world test to get started.

Some differences from spasm to keep in mind

In spasm preprocessor commands start with #, but fasmg does not have a preprocessor so the corresponding commands are not prefixed with #.

In spasm directives start with a dot, in fasmg directives omit the dot because identifiers starting with a dot are used for local labels, which are used like this:

    ld b,(hl)
    inc hl
    ld a,(hl)
    call _PutC
    djnz .loop
This example creates a label which can be referenced with PutStrLen.loop anywhere, and .loop anywhere between PutStrLen: and the next non-local label definition.

Be careful reusing instruction/register names as other symbols in fasmg: things like add = 5 and ld: won't compile because they look like instructions, and a = 42will hide the symbol a so that you can't use that case variant to refer to the a register anymore.

While spasm will sometimes silently drop forward references, fasmg allows you to forward reference anything that is assigned exactly once (including labels, which can't be defined multiple times) and anything else you get the last assigned value, or an error if it hasn't been assigned the first time yet.

Fasmg does not support inline macros, instead you can call a macro that defines a result:

#define sum(a,b) a+b
ld a,sum(1,2)
macro sum r,a,b
    r = a + b
end macro
sum temp,1,2
ld a,temp

Fasmg does not parse backslash escapes in strings. Instead, define them outside a string using numbers or equates, and to include the quote delimiter character (" or ', whichever one you happened to use, they are interchangeable) inside a string, simply double it (e.g. db 9,"a'b""c",'d"e''f',13,10,0 produces the c string "\ta\'b"cd"e\'f\r\n").

In fasmg, a line that ends with \ continues onto the next line. Unlike spasm, \ cannot be used to put multiple lines into one line (though, as usual, you could always just write a macro to support that if you wanted it).

Spasm strictly evaluates things from left-to-right with signed 32-bit integer arithmetic, but fasmg has a slightly unusual order of operations on linear polynomials with infinite precision 2-adic integer coefficients.

Porting spasm commands to fasmg

    #include "file"
        include "file"
    #import "file"
        file "file"
    #end comment
        if 0
        end if
    #define x y
    #defcont z
        define x y \
    #if x
    #elif y
        if x
        else if y
        end if
    #ifdef x
        if defined x
            ; note that this will still run even if x is defined (once) later, so do not use for include guard
        end if
        if ~defined x
            ; note that this will not run even if x is defined (once) later, so do not use for include guard
        end if
    #undef x
    #undefine x
        while defined x
            restore x
        end while
    #macro x(y,z)
        macro x y,z
        end macro
    .addinstr ...
        ; You have to write a macro that parses the arguments and emits the correct code.
    .block x
    .fill x
        rb x
    .byte x
    .db x
        db x
    .dw x
        dw xx
    .dl x
        dl x
    .echo "str"
        display "str",10
    .echo 123
        repeat 1,x:0
            display `x,10
        end repeat
        ; Ignored in spasm anyway
    .error "string ",5
        repeat 1,x:5
            err 'string',`x
        end repeat
    .fill x,y
        db x dup y
        ; fasmg does not support listing ootb, but surprisingly could be done with macros.
    .org userMem-2
    .db tExtTok,tAsm84CeCmp
        ; This is done automatically in for executable files.
    .org x
        org x
    .seek x
    .db y
        store y : byte at x
    x .equ y
        x = y

Extra features in

jq works just like jr/jp except it outputs nothing if you are trying to jump to the address following the instruction, a jr if possible, otherwise a jp. Note that because fasmg is a multi pass assembler, it should generally find the optimal solution to this (e.g. if converting a jp to a jr brings another jp into jr range). Please don't try to use pc relative targets (e.g. jr $+3) around jq as you may not actually know what size the jq will end up being!

A contiguous sequence of push/pop instructions can be coalesced to a single instruction with each register a separate argument, for example:

    push af, bc, de, hl
        call routine
    pop hl, de, bc, af
is the same as
    push af
        push bc
            push de
                push hl
                    call routine
                pop hl
            pop de
        pop bc
    pop hl

Instructions that take a bit index as an argument accept numbers outside the range 0-7 in certain cases. For example, bit 8,hl is the same as bit 0,h and bit -1,(ix-10) is the same as bit 7,(ix-11). Note that things that don't correspond to a single instruction like bit 12,(hl) and bit 16,hl won't assemble.

ZDS pseudo instructions are currently supported, but may be moved to a separate file in the future.

FASMG Quick Reference

Each line is a precedence level, except where specified otherwise, operators within a precedence level are evaluated ltr.
Logical expressions:
~                                    unary logical not, arguments can also be
& |                                  binary logical conjunction and disjunction
All the operators below this line can only have basic expressions as arguments
= < > <= >= <> eq eqtype relativeto  comparison, <> is not equal, eq is like = but returns false for uncomparable operands, eqtype returns true if both operands are the same (algebraic, string, float) type, relativeto returns true if both operands are comparable (differ by a constant)
defined used                         true if the basic expression to the right is entirely defined, true if the variable name to the right is used

Basic expressions:
lengthof elementsof float trunc      length of a string in characters, length of a poly in terms, int to float, float to int
sizeof elementof scaleof metadataof  rtl size associated with a label, poly op idx
       element   scale   metadata    idx op poly
not bsf bsr                          complement, index of lowest set bit, index of highest set bit
shl shr bswap                        shifts, byte swap (second arg is the size of the value to swap, in bytes)
xor and or                           bitwise
mod                                  remainder
* /
+ -                                  includes unary!
string                               converts a number to a string (strings are usually converted to numbers implicitly, or explicitly with a unary + operator)

Builtin symbolic variables:
<name>#<name> ; concatenates two names into a single identifier, but each side may get replaced individually if it matches a parameter name
$ ; current pc
$$ ; base of the current output section (address passed to last org)
$@ ; address after last non-reserved data
$% ; offset within output file, does not work with
$%% ; offset after last non-reserved data within output file, does not work with
%t ; assembly timestamp
__file__ ; current file
__line__ ; current line

org <basic expr> ; start a new output area to appear next in the file and assembled starting at address <basic expr>
section <basic expr> ; same as org but do not output reserved bytes, does not work with
virtual [<basic expr>] ; start a new output area that does not get output to the file
end virtual ; restore the previous output area
<name>:: ; creates an area label that references the current output area
load <name>[ : <size>] from [<area label> : ]<addr> ; loads <size> (defaults to sizeof <addr>) outputted bytes from address <addr> in output area <area label> (defaults to current output area) and store in variable <name>
store <name>[ : <size>] at [<area label> : ]<addr> ; stores <size> (defaults to sizeof <addr>) bytes to address <addr> in output area <area label> (default to current output area) and store in variable <name>

db <basic expr 1>[, <basic expr 2>...] ; define 1-byte values
rb <basic expr> ; reserve <basic expr> 1-byte locations
dw <basic expr 1>[, <basic expr 2>...] ; define 2-byte values
rw <basic expr> ; reserve <basic expr> 2-byte locations
dl <basic expr 1>[, <basic expr 2>...] ; define 3-byte values
rl <basic expr> ; reserve <basic expr> 3-byte locations
dd <basic expr 1>[, <basic expr 2>...] ; define 4-byte values
rd <basic expr> ; reserve <basic expr> 4-byte locations
emit|dbx <size> : <basic expr 1>[, <basic expr 2>...] ; define <size>-byte values
; If any reserve or definition is preceded by a name with no colon, that name gets defined as a label to the first item, with a sizeof the item size
; Inside any definition:
    <basic expr 1> rep <basic expr 2> ; repeats <basic expr 2> <basic expr 1> times, to include more than one value in the repetition, enclose them with <>

<name> equ <anything> ; define an arbitrary text substitution for a symbol
<name> reequ <anything> ; same as equ but don't discard previous value
define <name> <anything> ; same as equ, but <anything> is not checked for recursive substitutions until use
redefine <name> <anything> ; same as define but don't discard previous value
<name> = <basic expr> ; assign a value to a symbol, discarding current value
<name> =: <basic expr> ; assign a value to a symbol, remembering the current value
<name> := <basic expr> ; assign a value to a constant symbol only once, attempts to redefine will error, therefore it can always be forward referenced
<name>: ; like <name> := $
label <name>[ : <size>][ at <addr>] ; defines a constant symbol with <size> size at address <addr> (defaults to $)
restore <name 1>[, <name 2>...] ; restore the previously remembered value for each symbol
; namespaces can be created by assigning anything to any symbol
namespace <name 1> ; switches to, but does not create, the namespace associated with the symbol <name>
    ; all new symbols <name 2> defined in here can be referenced outside the namespace block with <name 1>.<name 2>
end namespace

macro <name> [<param 1>[, <param 2>...]] ; defines a macro, remembering the current contents
    ; macro body, parameters get substituted with their values every time the macro is executed
end macro
purge <name> ; restores the previously remembered contents of a macro
struc <name> [(<label name>) ][<param 1>[, <param 2>...]] ; defines a labeled macro, remembering the current contents
    ; macro body, parameters get substituted with their values every time the macro is executed, Both . and <label name> refer to the label
end struc
restruc <name> ; restores the previously remembered contents of a labeled macro
; macro and struc args can be suffixed with * to mean required,  : <basic expr> to give a default value if not specified, and & on the last argument means it takes on the value of all the remaining arguments
local <name 1>[, <name 2>...] ; makes the specified symbols local to the current macro or struc invocation
esc macro ... ; exactly like macro, but does not require an extra end macro to end the enclosing macro
esc end macro ; exactly like end macro, but does not close the enclosing macro even if there was no opening macro

[else ]if <cond expr>
    ; run these commands if <cond expr> is true, with else only if the previous command was false or didn't match
[else ]match <pattern>, <anything>
    ; run these command if <pattern> matches <anything>, with else only if the previous command was false or didn't match
[else ]rmatch <pattern>, <anything>
    ; run these command if <pattern> matches <anything>, discarding context, with else only if the previous if was false or match didn't match
    ; run these commands if previous if was false or match didn't match
end if|match|rmatch ; ends if, match, or rmatch, use whichever was used last (before the optional else)
while <cond expr>
    ; run these commands while <cond expr> is true
end while
repeat <basic expr>[, <name 1> : <basic expr 1>[, <name 2> : <basic expr 2>...]]
    ; run these commands <basic expr> times, symbols start at their initial value and go up by one each iteration
end while
iterate|irp <name>[, <first>[, <second>...]]
    ; run these commands
end iterate|irp
irpv <name 1>, <name 2>
    ; run these commands with name 1 equal to each remembered value of name 2, starting from the oldest, only works with define/equ
end irpv
; Inside all looping commands:
    % ; current iteration starting from 1
    %% ; total iterations
    indx <basic expr> ; switches to a different iteration index
    break ; break out of loop

include '<file>' ; assembles <file> at the current location in the source
file '<file>'[ : <start>[, <size>]] ; outputs <size> (defaults to entire file) bytes from a file starting at byte <start> (defaults to beginning)
display <basic expr 1>[, <basic expr 2>...] ; outputs strings as strings and numbers as characters to stdout
eval <basic expr 1>[, <basic expr 2>...] ; same syntax as display, evaluates the concatenation of all the arguments as assembly code at the current location in the source
err <basic expr 1>[, <basic expr 2>...] ; same syntax as display, displays a custom error the causes assembly to fail if this is the last pass
assert <cond expr> ; causes assembly to fail if <cond expr> is false on the last pass

Please ask any questions in this thread about things that don't work as you expect, or about how best to do some specific idiom, or port some snippet of spasm code, since many of fasmg's most powerful features are not very obvious, and certainly very different from other assemblers. I will update this post with things as I think of them or as they come up.
Very nice work! I have a few questions:

Can macro names have a '-' character in them?
Where must include files be placed?
None of +-/*=<>()[]{}:?!,.|&~#`\ can be used in symbols. Include file paths are tried relative to the current file being assembled, the current working directory, and all of the paths in the INCLUDE environment variable separated by ;.
How would I write a macro that takes a single string argument and converts the string to uppercase? Also, how do I convert an integer to a string?

macro toupper result, value*
    local v, c
    v = value
    repeat lengthof v
        c = v shr (8*(%-1)) and $ff
        if c >= 'a' & c <= 'z'
            v = v xor ('A' xor 'a') shl (8*(%-1))
        end if
    end repeat
    result = string v
end macro

Another way to do the same thing, that doesn't trim trailing null chars:

macro toupper result, value*
    local c
        db value
        repeat $ - $$
            load c: byte from $ + %-1
            if c >= 'a' & c <= 'z'
                store c and not $20: byte to $ + %-1
            end if
        end repeat
        load result: $ - $$ from $$
    end virtual
end macro

If you want to convert a positive integer to a decimal string, you can use the following construct:

repeat 1, temp: value
    result = temp
end repeat

This says to repeat the block one time with an iterating variable named temp that starts at value, and because it is a replacement variable (not a symbolic variable), you can use ` to convert the contents of the variable into a string. For something more complicated, you have to write out the logic, for example:

macro int2str result, value*, base: 10
    local v, p, b, d, r
        v = value
        if v < 0
            db '-'
            v = -v
        end if
        p = 1
        b = base
        if b < 2
            err 'invalid base'
        end if
        while p * b <= v
            p = p * b
        end while
        while p
            d = (v / p) mod b
            if d < 10
                db '0' + d
                db 'A' + d - 10
            end if
            p = p / b
        end while
        load result: $ - $$ from $$
    end virtual
end macro
int2str string, -123, 12
display string, 10
Now that I'm still completely inept but not horribly incompetent with this now; I would just like to point out that this is the best assembler I have ever used. It has support for anything you can dream of, is easy to use once you get used to its new syntax, and can output pretty much anything. Heck; you could build a compiler with this thing. Awesome work as always jacobly Smile
For anyone else interested in learning about how fasmg macros are built: jacobly recently created the topic Useful fasmg macros. His first post demonstrates a macro for defining enums. I asked him to explain how it works, and a lengthy IRC conversation giving a pretty thorough explanation followed.

For posterity, here's the content of jacobly's post as it was at the time of the conversation:

jacobly wrote:
macro enum? enumeration
   local next
   next = 0
   match any, enumeration
      struc (enumerator) ? value := next &
         namespace enumeration
            enumerator :value
            next = enumerator + 1
            enumeration equ enumerator
         end namespace
      end struc
      struc ? value := next &
         . :value
         next = . + 1
      end struc
   end match
   macro end?.enum?!
      restruc ?
      purge end?.enum?
   end macro
end macro


enum NAME
   VALUE3 = 3
   VAL1 = VALUE1
end enum
db NAME.VALUE0, NAME.VALUE1, NAME.VALUE3, NAME.VALUE4, NAME.VAL1 ; same as db 0, 1, 3, 4, 1
irpv each, NAME
   db `each, each
end irpv ; same as db 'VALUE0', 0, 'VALUE1', 1, 'VALUE3', 3, 'VALUE4', 4, 'VAL1', 1

NAME can be ommitted to put the enumerators in the global namespace.

And here's the IRC log of him explaining it to me, a relative fasmg newbie equipped with the fasmg manual to assist:

#cemetech wrote:
[2018-02-09 08:04:54] <Runer112> jacobly: now please explain how the hell that works
[2018-02-09 08:05:04] <jacobly> you don't want to know
[2018-02-09 08:05:17] <Runer112> I do want to know
[2018-02-09 08:05:20] <jacobly> one of the := is a misnomer
[2018-02-09 08:05:32] <Runer112> if I can't make macros, I won't really want to use fasmg
[2018-02-09 08:06:11] <jacobly> line 1: declare a macro called case-insensitive enum with at most one optional parameter case-sensitive enumeration
[2018-02-09 08:06:31] <Runer112> okay stop
[2018-02-09 08:06:41] <Runer112> ? = case insensitive?
[2018-02-09 08:06:41] <jacobly> idk what you want to know :P
[2018-02-09 08:06:52] <jacobly> ? after an identifier means case insensitive
[2018-02-09 08:06:55] <Runer112> and parameters are optional by default?
[2018-02-09 08:06:58] <jacobly> yes
[2018-02-09 08:07:20] <jacobly> there are four types of parameters
[2018-02-09 08:08:24] <jacobly> optional (replaced with nothing if parameter not passed), default (assigned a default value if parameter not passed), required (error if parameter not passed), greedy (consume all remaining parameters)
[2018-02-09 08:09:06] <Runer112> alright
[2018-02-09 08:09:53] <Runer112> so what does `enumeration` capture
[2018-02-09 08:10:02] <Runer112> err
[2018-02-09 08:10:09] <jacobly> macro name optional , default : value , required * , greedy &
[2018-02-09 08:10:12] <Runer112> I guess you said, but does that capture just the name?
[2018-02-09 08:10:35] <jacobly> enumeration captures everything that comes after the macro invocation
[2018-02-09 08:10:53] <jacobly> any commas will error
[2018-02-09 08:11:06] <jacobly> unless there's an outer <>
[2018-02-09 08:11:16] <Runer112> so that captures basically the entire body
[2018-02-09 08:11:20] <Runer112> and the name
[2018-02-09 08:11:36] <jacobly> not the name
[2018-02-09 08:12:26] <jacobly> let's say you have "macro name param"
[2018-02-09 08:12:31] <jacobly> err
[2018-02-09 08:12:38] <jacobly> macro name param
[2018-02-09 08:12:39] <jacobly> param
[2018-02-09 08:12:41] <jacobly> end macro
[2018-02-09 08:12:56] <jacobly> name anything-but-comma
[2018-02-09 08:13:00] <jacobly> expands to
[2018-02-09 08:13:02] <jacobly> anything-but-comma
[2018-02-09 08:13:07] <Runer112> that makes sense, yes
[2018-02-09 08:13:09] <jacobly> name <anything>
[2018-02-09 08:13:10] <jacobly> expands to
[2018-02-09 08:13:13] <jacobly> anything
[2018-02-09 08:13:52] <Runer112> so... enumeration should be the enum name and all the values
[2018-02-09 08:14:08] <jacobly> err
[2018-02-09 08:14:32] <jacobly> enumeration is the identifier within the macro body that gets replaced at each macro invocation
[2018-02-09 08:14:39] <Runer112> I thought that was basically what I said before, but perhaps there was confusion as to which name was being considered
[2018-02-09 08:14:45] <jacobly> indeed
[2018-02-09 08:15:23] <jacobly> maybe by captures the entire body you mean in scope throughout the macro body
[2018-02-09 08:15:29] <jacobly> in which case also yes
[2018-02-09 08:16:33] <jacobly> it captures the entire macro invocation line, minus the macro name itself
[2018-02-09 08:19:17] <jacobly> Runer112, should I move on?
[2018-02-09 08:19:36] <Runer112> I'm still confused
[2018-02-09 08:19:55] <Runer112> I'm looking at the fasmg docs now and, looking at that, it looks like `enumeration` should capture just "NAME"
[2018-02-09 08:20:09] <jacobly> yes
[2018-02-09 08:20:16] <jacobly> I said the entire line
[2018-02-09 08:20:21] <jacobly> not the next line
[2018-02-09 08:20:25] <Runer112> and none of the value definitions
[2018-02-09 08:20:31] <jacobly> correct
[2018-02-09 08:20:51] <jacobly> fasmg never does things across lines
[2018-02-09 08:20:53] <Runer112> oh
[2018-02-09 08:21:47] <Runer112> yeah, I wasn't getting the concept that the macro doesn't just parse everything at once
[2018-02-09 08:22:13] <Runer112> and that instead adds new parsing rules for its scope
[2018-02-09 08:22:48] <jacobly> the macro body replaces the macro invocation line
[2018-02-09 08:23:08] <jacobly> and all it does is define some more macros
[2018-02-09 08:23:14] <jacobly> well symbols
[2018-02-09 08:23:26] <Runer112> oh right
[2018-02-09 08:23:27] <Runer112> there isn't an official scope
[2018-02-09 08:23:27] <Runer112> it gets cleaned up by the end enum macro
[2018-02-09 08:23:53] <jacobly> scopes work by pushing and popping chains of values
[2018-02-09 08:24:21] <jacobly> purge name discards the latest and brings back the previous macro definition of name
[2018-02-09 08:25:55] <jacobly> macro name saves the current and pushes a new macro definition for name
[2018-02-09 08:26:09] <Runer112> alright, so I get line one, huzzah
[2018-02-09 08:26:55] <jacobly> local only works inside (labeled) macros
[2018-02-09 08:26:59] <Runer112> I mostly get the next 3 lines as well
[2018-02-09 08:27:18] <jacobly> it gives an identifier a unique context per macro invocation
[2018-02-09 08:27:28] <jacobly> the local can appear anywhere within the macro body
[2018-02-09 08:27:50] <jacobly> understanding context is the key to understanding advanced fasmg
[2018-02-09 08:27:59] <Runer112> it's basically just a local variable, isn't it
[2018-02-09 08:28:06] <Runer112> nothing about it seems weird
[2018-02-09 08:28:29] <jacobly> well it's accessible within declared macros even though they aren't expanded until outside the macro body
[2018-02-09 08:28:47] <Runer112> yes, that makes sense though
[2018-02-09 08:28:58] <Runer112> because they were declared inside the scope where it was local
[2018-02-09 08:29:29] <jacobly> but if you pass next as a parameter to a macro defined inside a macro with a local variable named next, it won't use the local
[2018-02-09 08:29:38] <Runer112> yes, that also makes sense
[2018-02-09 08:30:08] <jacobly> basically identifiers are usually resolved where they came from, not where they are used
[2018-02-09 08:30:26] <Runer112> this all follows what I'd expect of a normal language
[2018-02-09 08:30:37] <Runer112> macros aren't just dumb text replacement
[2018-02-09 08:31:11] <jacobly> so you understand the match?
[2018-02-09 08:31:30] <Runer112> looks like it basically tests is `enumeration` is not blank
[2018-02-09 08:31:37] <Runer112> if*
[2018-02-09 08:31:43] <jacobly> yes, it's basically testing whether enumeration, after expansion, is not black
[2018-02-09 08:31:47] <jacobly> wow
[2018-02-09 08:31:49] <jacobly> *blank
[2018-02-09 08:32:08] <Runer112> oh, I guess that's a good question
[2018-02-09 08:32:13] <Runer112> when is stuff expanded
[2018-02-09 08:32:24] <jacobly> in match
[2018-02-09 08:33:06] <jacobly> and really late after parameter replacement before expression evaluation
[2018-02-09 08:33:35] <jacobly> also for equ but not define
[2018-02-09 08:33:58] <Runer112> alright
[2018-02-09 08:34:49] <jacobly> examples of parameter replacement are enumeration, any, enumerator, value
[2018-02-09 08:35:51] <jacobly> so macro, struc, iterate, repeat, irpv can all define parameters
[2018-02-09 08:36:13] <jacobly> also match
[2018-02-09 08:37:15] <jacobly> parameters are expanded early, symbols are expanded late
[2018-02-09 08:38:28] <jacobly> so parameters can be expand to macros, but symbols cannot
[2018-02-09 08:38:48] <jacobly> you can use match to expand a symbol early
[2018-02-09 08:39:50] <jacobly> define late db 0
[2018-02-09 08:39:58] <jacobly> late ; Error: illegal instruction.
[2018-02-09 08:40:04] <jacobly> match early, late
[2018-02-09 08:40:13] <jacobly> early ; db 0
[2018-02-09 08:40:15] <jacobly> end match
[2018-02-09 08:41:33] <Runer112> okay
[2018-02-09 08:42:28] <jacobly> so else match and else if can all be chained together, with the final end matching the latest else
[2018-02-09 08:42:52] <jacobly> if a \ else match b, c \ else if d \ else match e, f \ end match
[2018-02-09 08:43:13] <jacobly> with the optional else at the end
[2018-02-09 08:43:23] <Runer112> got it
[2018-02-09 08:43:33] <jacobly> alright now to struc
[2018-02-09 08:43:46] <jacobly> struc is the same as a macro, but it is recognized as the second symbol
[2018-02-09 08:44:16] <jacobly> so in "define x y" define is a macro, but in "x equ y" equ is a labeled macro which can be created with struc
[2018-02-09 08:45:11] <Runer112> makes sense
[2018-02-09 08:45:54] <jacobly> every line must match ( <label> ':' )? ( <macro> <arg>* | <label> <struc> <arg>* ) to assemble
[2018-02-09 08:46:21] <Runer112> okay
[2018-02-09 08:46:25] <jacobly> where <label> is an undefined identifier, <macro> is a macro identifier, and <struc> is a struc identifier
[2018-02-09 08:47:07] <jacobly> except when you have a macro or struc called ? active, which changes things
[2018-02-09 08:47:27] <Runer112> yes, I saw that that handles otherwise illegal lines
[2018-02-09 08:47:30] <jacobly> they basically catch lines that would error
[2018-02-09 08:47:45] <jacobly> struc ?! catches every line
[2018-02-09 08:47:50] <jacobly> err no
[2018-02-09 08:47:52] <jacobly> macro ?! catches every line
[2018-02-09 08:48:00] <jacobly> macro ? catches lines that don't start with a known macro
[2018-02-09 08:48:24] <jacobly> *and only while assembly is active
[2018-02-09 08:48:44] <jacobly> eh, this is complicated
[2018-02-09 08:49:01] <Runer112> told you
[2018-02-09 08:49:20] <jacobly> so there's conditional and unconditional macros
[2018-02-09 08:49:30] <jacobly> macro name! creates an unconditional macro
[2018-02-09 08:49:59] <jacobly> conditional macros only expand when assembly is active, otherwise do nothing
[2018-02-09 08:50:14] <jacobly> unconditional macros always expand, but assembly is still inactive inside
[2018-02-09 08:51:13] <jacobly> "db 0" is a conditional macro because it does nothing when inactive
[2018-02-09 08:51:30] <jacobly> "end if" is an unconditional macro because it expands regardless of the condition
[2018-02-09 08:51:51] <Runer112> got it
[2018-02-09 08:53:10] <jacobly> macro ?! catches every line
[2018-02-09 08:53:27] <jacobly> macro ? catches every active line that isn't an unconditional macro
[2018-02-09 08:54:04] <jacobly> struc ?! catches every line that doesn't start with a macro
[2018-02-09 08:54:19] <jacobly> struc ? catches every active line that isn't a macro
[2018-02-09 08:54:25] <Runer112> macro ? catches active lines that are coniditonal macros?
[2018-02-09 08:54:34] <jacobly> yes
[2018-02-09 08:55:08] <jacobly> I'm a bit iffy on what struc ?! does with inactive conditional macros
[2018-02-09 08:55:11] <Runer112> so it basically overwrites all other conditional macros
[2018-02-09 08:55:17] <Runer112> until it's purged
[2018-02-09 08:55:25] <jacobly> yes
[2018-02-09 08:55:37] <Runer112> okay
[2018-02-09 08:55:50] <jacobly> the entire line is passed as arguments
[2018-02-09 08:56:40] <jacobly> macro ? line&
[2018-02-09 08:56:42] <jacobly> line
[2018-02-09 08:56:43] <jacobly> end macro
[2018-02-09 08:56:48] <jacobly> does nothing
[2018-02-09 08:57:23] <Runer112> it disables all other conditional macros though?
[2018-02-09 08:57:42] <jacobly> it overrides them but the body calls them anyway
[2018-02-09 08:57:56] <Runer112> ?
[2018-02-09 08:58:47] <jacobly> if you did db 0 after that macro definition, "db 0" gets passed to the ? macro which expands to "db 0" which expands to the conditional macro db
[2018-02-09 08:58:58] <Runer112> what's stopping it from infinitely recurring
[2018-02-09 08:59:04] <jacobly> the fact that it doesn't
[2018-02-09 08:59:11] <Runer112> just a special rule?
[2018-02-09 08:59:21] <jacobly> basically macro ? is disabled inside its own body
[2018-02-09 08:59:39] <Runer112> other macros aren't, though?
[2018-02-09 08:59:41] <jacobly> but it's not a special rule
[2018-02-09 09:00:12] <jacobly> the only special rule about macro ? is that it can't be forward referenced
[2018-02-09 09:00:32] <Runer112> well that makes sense
[2018-02-09 09:00:49] <jacobly> and forward references can't recurse
[2018-02-09 09:01:10] <jacobly> err
[2018-02-09 09:01:15] <jacobly> backwards references can't recures
[2018-02-09 09:01:37] <jacobly> the only way to recurse is to define a macro exactly once and then forward reference it within its own body
[2018-02-09 09:02:20] <Runer112> otherwise, its previous definition will be active in its body?
[2018-02-09 09:02:24] <jacobly> correct
[2018-02-09 09:02:35] <Runer112> weird, but okay
[2018-02-09 09:02:36] <jacobly> this allows you to do things like
[2018-02-09 09:02:44] <jacobly> macro db x
[2018-02-09 09:02:48] <jacobly> db x + 1
[2018-02-09 09:02:48] <jacobly> end macro
[2018-02-09 09:02:58] <jacobly> err that doesn't work or make sense
[2018-02-09 09:03:11] <jacobly> but that's more or less the idea
[2018-02-09 09:03:39] <Runer112> yeah that would just be a infinite recursion wouldn't it
[2018-02-09 09:03:48] <jacobly> probably
[2018-02-09 09:04:04] <jacobly> yes
[2018-02-09 09:04:10] <Runer112> unless your plan was to basically alter a previous db to autoincrement the value
[2018-02-09 09:04:21] <jacobly> with ? it would auto increment the value
[2018-02-09 09:04:42] <Runer112> huh
[2018-02-09 09:05:02] <jacobly> case-sensitive identifiers override case-insensitive ones
[2018-02-09 09:05:43] <jacobly> a? = 0
[2018-02-09 09:05:45] <jacobly> a = 1
[2018-02-09 09:05:55] <jacobly> now a is 1, A is 0, a? is 0, and A? is 0
[2018-02-09 09:06:14] <Runer112> wit
[2018-02-09 09:06:21] <Runer112> wut*
[2018-02-09 09:06:34] <jacobly> a? = 0 defines a case-insensitive symbol
[2018-02-09 09:06:40] <jacobly> a = 1 defines a case-sensitive symbol
[2018-02-09 09:06:57] <jacobly> if the case matches a case-sensitive symbol, that one takes priority
[2018-02-09 09:07:09] <jacobly> otherwise case-insensitive symbols are searched
[2018-02-09 09:07:27] <jacobly> but ? after the symbol forces it to look for a case-insensitive symbol
[2018-02-09 09:07:38] <Runer112> super weird, but okay
[2018-02-09 09:08:07] <jacobly> so to override a builtin, you can't forget the ?
[2018-02-09 09:08:34] <Runer112> wait what, shouldn't it be the other way around
[2018-02-09 09:08:46] <jacobly> all builtins are case insensitive
[2018-02-09 09:09:00] <jacobly> if you forget the ? you created a new symbol
[2018-02-09 09:09:01] <Runer112> okay
[2018-02-09 09:09:14] <Runer112> but it'll still work if the case matches?
[2018-02-09 09:09:34] <jacobly> well the recursion works differently as you saw
[2018-02-09 09:09:39] <jacobly> same with symbolic recursion
[2018-02-09 09:09:56] <Runer112> oh I see
[2018-02-09 09:10:16] <Runer112> err maybe
[2018-02-09 09:10:23] <jacobly> aha I thought of an example
[2018-02-09 09:10:39] <jacobly> err nvm
[2018-02-09 09:10:40] <Runer112> because it's now a new symbol, even though there is an old db? definition, db still recurs
[2018-02-09 09:11:02] <jacobly> yes, it wouldn't recurse if you put a ? on the second db, but then DB would skip the macro
[2018-02-09 09:11:04] <Runer112> but DB would not recur
[2018-02-09 09:11:10] <jacobly> correct
[2018-02-09 09:11:27] <Runer112> alright
[2018-02-09 09:12:01] <jacobly> so labeled macros match the second identifier if the first identifier didn't match a macro
[2018-02-09 09:12:20] <jacobly> they act as if that label was defined, except it isn't
[2018-02-09 09:12:48] <jacobly> a:
[2018-02-09 09:12:49] <jacobly> .b:
[2018-02-09 09:12:57] <jacobly> that defines a and a.b to both be $
[2018-02-09 09:13:14] <jacobly> struc name
[2018-02-09 09:13:17] <jacobly> .:
[2018-02-09 09:13:19] <jacobly> .b:
[2018-02-09 09:13:20] <jacobly> end struc
[2018-02-09 09:13:24] <jacobly> a name
[2018-02-09 09:13:28] <jacobly> than does the same thing
[2018-02-09 09:13:33] <jacobly> *that
[2018-02-09 09:13:42] <Runer112> okay
[2018-02-09 09:14:19] <jacobly> if you put an identifier in parentheses after struc it becomes a parameter that gets replaced with the label
[2018-02-09 09:15:07] <jacobly> so now do you understand the next line up to the question mark?
[2018-02-09 09:15:07] <Runer112> alright
[2018-02-09 09:15:59] <Runer112> err give me a few minutes, I've been on my phone and don't have the code here
[2018-02-09 09:16:12] <Runer112> be back at a computer soon
[2018-02-09 09:19:49] <Runer112> back
[2018-02-09 09:20:08] <Runer112> okay, so struc makes sense up until &
[2018-02-09 09:20:25] <jacobly> it shouldn't
[2018-02-09 09:20:31] <Runer112> oh wait
[2018-02-09 09:20:33] <Runer112> no it doesn't
[2018-02-09 09:20:43] <Runer112> where's value coming from
[2018-02-09 09:21:00] <jacobly> so ignore everything from : onwards
[2018-02-09 09:21:09] <jacobly> now what does it look like
[2018-02-09 09:21:37] <Runer112> like value is a param
[2018-02-09 09:21:41] <jacobly> yes
[2018-02-09 09:22:19] <jacobly> so struc params matches everything after the struc name (in this case after the label since it's a struc ?)
[2018-02-09 09:23:16] <jacobly> so if you look at the usage, line 2 value will match "" and in line 4 value will match "= 3"
[2018-02-09 09:23:31] <jacobly> s/match/capture/
[2018-02-09 09:23:57] <Runer112> okay
[2018-02-09 09:24:03] <Runer112> why is & not necessary here
[2018-02-09 09:24:11] <jacobly> it is there
[2018-02-09 09:24:26] <Runer112> ohhhhhh
[2018-02-09 09:24:31] <jacobly> but it isn't technically necessary because we don't expect a comma
[2018-02-09 09:24:41] <jacobly> so I should probably remove it
[2018-02-09 09:24:43] <Runer112> so := next doesn't mean assign that value
[2018-02-09 09:24:50] <Runer112> it means its default value is = next
[2018-02-09 09:24:56] <jacobly> yes
[2018-02-09 09:25:02] <jacobly> that line is very mean
[2018-02-09 09:25:20] <Runer112> I was going to say, i'm pretty sure := is a thing
[2018-02-09 09:25:35] <jacobly> value: = next
[2018-02-09 09:25:36] <Runer112> this is like the downto operator in C
[2018-02-09 09:25:54] <jacobly> value : < = next >
[2018-02-09 09:26:28] <jacobly> would all do the same thing
[2018-02-09 09:26:33] <Runer112> and to clarify
[2018-02-09 09:26:55] <Runer112> <> is to commas in fasmg as "" is to spaces in a shell
[2018-02-09 09:27:04] <jacobly> sort of
[2018-02-09 09:27:14] <jacobly> <> must be on the outside to escape the commas
[2018-02-09 09:27:26] <jacobly> test <a, b, c>
[2018-02-09 09:27:30] <jacobly> has one parameter
[2018-02-09 09:27:40] <jacobly> test x < a, b, c > d
[2018-02-09 09:27:45] <jacobly> has 3 parameters
[2018-02-09 09:27:54] <Runer112> right
[2018-02-09 09:28:07] <Runer112> because you can do crap like x"a b c"d in a shell
[2018-02-09 09:28:43] <Runer112> anyway
[2018-02-09 09:28:55] <Runer112> namespace
[2018-02-09 09:29:57] <jacobly> ooh
[2018-02-09 09:31:56] <Runer112> ?
[2018-02-09 09:32:23] <Runer112> oh
[2018-02-09 09:32:44] <Runer112> nevermind, namespace makes sense
[2018-02-09 09:33:46] <jacobly> stupid syntax highlighting
[2018-02-09 09:34:46] <jacobly> so anyway
[2018-02-09 09:35:23] <jacobly> enumeration.enumerator :value would have worked instead of namespace
[2018-02-09 09:35:33] <jacobly> but I use namespace for a particular reason
[2018-02-09 09:37:11] <jacobly> it allows me to bind enumerator to enumeration instead of enumeration.enumerator
[2018-02-09 09:38:30] <jacobly> I think you should understand everything down to end match now
[2018-02-09 09:39:09] <Runer112> umm, give me a sec
[2018-02-09 09:39:28] <jacobly> except for what I just added I guess
[2018-02-09 09:39:39] <Runer112> oh yeah, syntax highlighting ruined that
[2018-02-09 09:39:49] <jacobly> but I'm going to do it differently anyway
[2018-02-09 09:40:40] <Runer112> what's the purpose of the colon in `enumerator :value`
[2018-02-09 09:40:49] <jacobly> value starts with =
[2018-02-09 09:41:02] <Runer112> oh
[2018-02-09 09:41:03] <jacobly> so you get a :=
[2018-02-09 09:41:15] <Runer112> what's the difference between those again
[2018-02-09 09:41:31] <jacobly> = assigns a variable, := assigns a constant
[2018-02-09 09:41:55] <Runer112> okay
[2018-02-09 09:42:03] <jacobly> it's easy to remember because name: and name:=$ do the same thing
[2018-02-09 09:42:15] <jacobly> aka labels are constants
[2018-02-09 09:43:50] <Runer112> what's the deal with `enumeration equ enumerator`
[2018-02-09 09:44:09] <Runer112> looks like that expands to something like `NAME equ VALUE0`
[2018-02-09 09:44:16] <jacobly> correct
[2018-02-09 09:44:29] <Runer112> ??
[2018-02-09 09:44:46] <jacobly> it allows you to iterate over all of the enumerators
[2018-02-09 09:45:22] <jacobly> I almost always use equ to create lists
[2018-02-09 09:45:58] <Runer112> using variables like stacks is weird
[2018-02-09 09:46:19] <jacobly> well keep in mind they can be random accessed too
[2018-02-09 09:46:41] <Runer112> meaning?
[2018-02-09 09:46:53] <jacobly> you can get the fifth definition of the variable
[2018-02-09 09:47:03] <jacobly> from the number 5
[2018-02-09 09:47:21] <Runer112> syntax?
[2018-02-09 09:47:29] <jacobly> well it's not short
[2018-02-09 09:47:41] <jacobly> irpv value, variable
[2018-02-09 09:47:44] <Runer112> that's not exactly random access then is it
[2018-02-09 09:47:48] <jacobly> indx 5
[2018-02-09 09:48:04] <jacobly> ; use value
[2018-02-09 09:48:09] <jacobly> break
[2018-02-09 09:48:10] <jacobly> end irpv
[2018-02-09 09:48:15] <Runer112> that's quite literally not random access
[2018-02-09 09:48:22] <jacobly> yeah but you could create a random access macro if you needed to :P
[2018-02-09 09:49:07] <Runer112> random access kind of carries an implication of efficiency not related to index
[2018-02-09 09:49:11] <jacobly> my point being you can use symbolic vars as lists, arrays, or stacks
[2018-02-09 09:49:21] <Runer112> anyway, off topic
[2018-02-09 09:49:26] <jacobly> err yeah
[2018-02-09 09:49:39] <jacobly> idk what the running time is
[2018-02-09 09:51:08] <jacobly> I mean it certainly looks O(1)
[2018-02-09 09:51:24] <jacobly> err
[2018-02-09 09:51:54] <Runer112> depends how variable "stacks" are implemented under the hood
[2018-02-09 09:52:05] <Runer112> if they're linked lists or arrays
[2018-02-09 09:52:27] <jacobly> I mean the access time is basically the same on both ends
[2018-02-09 09:52:37] <jacobly> which doesn't actually imply O(1)
[2018-02-09 09:52:45] <Runer112> could be circular
[2018-02-09 09:52:49] <Runer112> and smart
[2018-02-09 09:53:01] <jacobly> it can't be circular, it's a tree
[2018-02-09 09:53:28] <Runer112> why is it a tree
[2018-02-09 09:53:34] <Runer112> and not a stack
[2018-02-09 09:53:44] <jacobly> well a spaghetti tree, whatever
[2018-02-09 09:54:06] <jacobly> I mean there could be a stack
[2018-02-09 09:54:16] <jacobly> which could have O(1) access
[2018-02-09 09:54:36] <Runer112> yes, a stack interface backed by an array
[2018-02-09 09:54:54] <Runer112> what the hell is a spaghetti tree
[2018-02-09 09:55:03] <jacobly> uhh, it's a backwards tree
[2018-02-09 09:55:16] <jacobly> every node points to it's parent
[2018-02-09 09:55:23] <jacobly> instead of its children
[2018-02-09 09:55:42] <Runer112> is there a 1:many relationship between parents and children
[2018-02-09 09:55:48] <jacobly> yes
[2018-02-09 09:55:54] <Runer112> why
[2018-02-09 09:56:47] <jacobly> a equ 0 \ a equ 1 \ restore a \ a equ 2
[2018-02-09 09:57:04] <jacobly> oh wait
[2018-02-09 09:57:10] <jacobly> I get it
[2018-02-09 09:57:17] <Runer112> unless you can revive 1, no need for a tree
[2018-02-09 09:57:34] <jacobly> yeah, that's why restore forbids forward reference
[2018-02-09 09:57:50] <jacobly> so yeah it's just a stack of values then
[2018-02-09 09:58:37] <Runer112> anyway, we've gotten way off track
[2018-02-09 09:58:48] <jacobly> symbols are definitely a spaghetti tree though >.>
[2018-02-09 09:59:19] <jacobly> also a hash table
[2018-02-09 09:59:22] <jacobly> tree
[2018-02-09 09:59:28] <jacobly> idk where we were :P
[2018-02-09 09:59:42] <Runer112> I'll just keep following the code and tell you where I get confused next
[2018-02-09 09:59:49] <jacobly> ?!
[2018-02-09 10:00:05] <jacobly> I guess you know what that means now
[2018-02-09 10:00:55] <Runer112> case-insensitive unconditional
[2018-02-09 10:04:03] <Runer112> is there a reason for `next = enumerator + 1` in preference to `next = value + 1`
[2018-02-09 10:04:29] <jacobly> just operator precedence
[2018-02-09 10:04:36] <jacobly> you could do next = (value) + 1 I guess
[2018-02-09 10:05:28] <jacobly> you should usually use () around macro parameters like in C for the same reason, but I usually use proxy locals instead
[2018-02-09 10:05:53] <Runer112> so how does the global namespace variant work
[2018-02-09 10:06:02] <Runer112> in particular, `.`
[2018-02-09 10:06:15] <jacobly> . is the same as enumerator
[2018-02-09 10:06:24] <jacobly> except it's a symbol that expands to .
[2018-02-09 10:06:55] <jacobly> but it "means" the same thing
[2018-02-09 10:08:48] <Runer112> okay, and it automagically expands to <label>.
[2018-02-09 10:08:56] <jacobly> it doesn't expand, but yes
[2018-02-09 10:10:04] <jacobly> . refers to the symbol associated with the current namespace, or the last label defined, or the label associated with a struc
[2018-02-09 10:10:38] <Runer112> alright
[2018-02-09 10:10:46] <Runer112> I think I follow everything now
How do I write to multiple output files?
Use virtual as.

From the fasmg manual:
The "virtual" creates a special output area which is not written into the main output file. This kind of area must reside between the "virtual" and "end virtual" commands, and after it is closed, the output generator comes back to the area it was previously operating on, with position and address the same as there were just before opening the "virtual" block. This allows also to nest the "virtual" blocks within each other.


"virtual" can also be followed by an "as" keyword and a string defining an extension of additional file where the initialized content of the area is going to be stored at the end of a successful assembly.

For example, if you're assembling into file.bin, to output to file.txt:

virtual as 'txt'
    db 'File contents here:'
end virtual
In this post from 2017 it said: "ZDS pseudo instructions are currently supported, but may be moved to a separate file in the future."

Are they still supported? If not is there a separate file with them in?

I'm busy trying to port stuff to the Agon Light computer, which also uses ez80 - and I am getting assembly issues with code coming from Zilog as it has this pseudo instructions in.

Thanks Paul
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum