diff --git a/README.md b/README.md index cefd534..1e76a42 100644 --- a/README.md +++ b/README.md @@ -1,472 +1 @@ -# Asm6502 - -6502 Macro Assembler in a single c++ file using the struse single file text parsing library. Supports most syntaxes. - -Every assembler seems to add or change its own quirks to the 6502 syntax. This implementation aims to support all of them at once as long as there is no contradiction. - -To keep up with this trend Asm6502 is adding the following features to the mix: - -1. Full expression evaluation everywhere values are used: [Expressions](#expressions) -2. C style scoping within '{' and '}': [Scopes](#scopes) -3. Reassignment of labels. This means there is no error if you declare the same label twice, but on the other hand you can do things like label = label + 2. -4. [Local labels](#labels) can be defined in a number of ways, such as leading period (.label) or leading at-sign (@label) or terminating dollar sign (label$). -5. [Directives](#directives) support both with and without leading period. -6. Labels don't need to end with colon, but they can. -7. No indentation required for instructions, meaning that labels can't be mnemonics, macros or directives. -8. Conditional assembly with #if/#ifdef/#else etc. -9. As far as achievable, support the syntax of other 6502 assemblers. - -In summary, if you are familiar with any 6502 assembler syntax you should feel at home with Asm6502. If you're familiar with C programming expressions you should be familiar with '{', '}' scoping and complex expressions. - -There are no hard limits on binary size so if the address exceeds $ffff it will just wrap around to $0000. I'm not sure about the best way to handle that or if it really is a problem. - -## Prerequisite - -Asm6502.cpp requires struse.h which is a single file text parsing library that can be retrieved from https://github.com/Sakrac/struse. - -### References - -* [6502 opcodes](http://www.6502.org/tutorials/6502opcodes.html) -* [6502 opcode grid](http://www.llx.com/~nparker/a2/opcodes.html) -* [Codebase64 CPU section](http://codebase64.org/doku.php?id=base:6502_6510_coding) - -## Features - -* **Code** -* **Comments** -* **Labels** -* **Directives** -* **Macros** -* **Expressions** - -### Code - -Code is any valid mnemonic/opcode and addressing mode. At the moment only one opcode per line is assembled. - -### Comments - -Comments are currently line based and both ';' and '//' are accepted as delimiters. - -### Expressions - -Anywhere a number can be entered it can also be interpreted as a full expression, for example: - -``` -Get123: - bytes Get1-*, Get2-*, Get3-* -Get1: - lda #1 - rts -Get2: - lda #2 - rts -Get3: - lda #3 - rts -``` - -Would yield 3 bytes where the address of a label can be calculated by taking the address of the byte plus the value of the byte. - -### Labels - -Labels come in two flavors: **Addresses** (PC based) or **Values** (Evaluated from an expression). An address label is simply placed somewhere in code and a value label is follwed by '**=**' and an expression. All labels are rewritable so it is fine to do things like NumInstance = NumInstance+1. Value assignments can be prefixed with '.const' or '.label' but is not required to be prefixed by anything. - -*Local labels* exist inbetween *global labels* and gets discarded whenever a new global label is added. The syntax for local labels are one of: prefix with period, at-sign, exclamation mark or suffix with $, as in: **.local** or **!local** or **@local** or **local$**. Both value labels and address labels can be local labels. - -``` -Function: ; global label - ldx #32 -.local_label ; local label - dex - bpl .local_label - rts - -Next_Function: ; next global label, the local label above is now erased. - rts -``` - -### Directives - -Directives are assembler commands that control the code generation but that does not generate code by itself. Some assemblers prefix directives with a period (.org instead of org) so a leading period is accepted but not required for directives. - -* [**ORG**](#org) (same as **PC**): Set the current compiling address. -* [**LOAD**](#load) Set the load address for binary formats that support it. -* [**ALIGN**](#align) Align the address to a multiple by filling with 0s -* [**MACRO**](#macro) Declare a macro -* [**EVAL**](#eval) Log an expression during assembly. -* [**BYTES**](#bytes) Insert comma separated bytes at this address (same as **BYTE** or **DC.B**) -* [**WORDS**](#words) Insert comma separated 16 bit values at this address (same as **WORD** or **DC.W**) -* [**TEXT**](#text) Insert text at this address -* [**INCLUDE**](#include) Include another source file and assemble at this address -* [**INCBIN**](#incbin) Include a binary file at this address -* [**CONST**](#const) Assign a value to a label and make it constant (error if reassigned with other value) -* [**LABEL**](#label) Decorative directive to assign an expression to a label -* [**INCSYM**](#incsym) Include a symbol file with an optional set of wanted symbols. -* [**POOL**](#pool) Add a label pool for temporary address labels -* [**#IF / #ELSE / #IFDEF / #ELIF / #ENDIF**](#conditional) Conditional assembly -* [**STRUCT**](#struct) Hierarchical data structures (dot separated sub structures) -* [**REPT**](#rept) Repeat a scoped block of code a number of times. - -**ORG** - -``` -org $2000 -(or pc $2000) -``` - -Sets the current assembler address to this address - -**LOAD** - -``` -load $2000 -``` - -For c64 .prg files this prefixes the binary file with this address. - -**ALIGN** - -``` -align $100 -``` - -Add bytes of 0 up to the next address divisible by the alignment - -**MACRO** - -See the 'Macro' section below - -**EVAL** - -Example: -``` -eval Current PC: * -``` -Might yield the following in stdout: -``` -Eval (15): Current PC : "*" = $2010 -``` - -When eval is encountered on a line print out "EVAL (\) \: \ = \" to stdout. This can be useful to see the size of things or debugging expressions. - -**BYTES** - -Adds the comma separated values on the current line to the assembled output, for example - -``` -RandomBytes: - bytes NumRandomBytes - { - bytes 13,1,7,19,32 - NumRandomBytes = * - ! - } -``` - -**byte** or **dc.b** are also recognized. - -**WORDS** - -Adds comma separated 16 bit values similar to how **BYTES** work. **word** or **dc.w** are also recognized. - -**TEXT** - -Copies the string in quotes on the same line. The plan is to do a petscii conversion step. Use the modifier 'petscii' or 'petscii_shifted' to convert alphabetic characters to range. - -Example: - -``` -text petscii_shifted "This might work" -``` - -**INCLUDE** - -Include another source file. This should also work with .sym files to import labels from another build. The plan is for Asm6502 to export .sym files as well. - -Example: - -``` -include "wizfx.s" -``` - - -**INCBIN** - -Include binary data from a file, this inserts the binary data at the current address. - -Example: - -``` -incbin "wizfx.gfx" -``` - -**CONST** - -Prefix a label assignment with 'const' or '.const' to cause an error if the label gets reassigned. - -``` -const zpData = $fe -``` - -**LABEL** - -Decorative directive to assign an expression to a label, label assignments are followed by '=' and an expression. - -These two assignments do the same thing (with different values): -``` -label zpDest = $fc -zpDest = $fa -``` - -**INCSYM** - -Include a symbol file with an optional set of wanted symbols. - -Open a symbol file and extract a set of symbols, or all symbols if no set was specified. Local labels will be discarded if possible. - -``` -incsym Part1_Init, Part1_Update, Part1_Exit "part1.sym" -``` - -**POOL** - -Add a label pool for temporary address labels. This is similar to how stack frame variables are assigned in C. - -A label pool is a mini stack of addresses that can be assigned as temporary labels with a scope ('{' and '}'). This can be handy for large functions trying to minimize use of zero page addresses, the function can declare a range (or set of ranges) of available zero page addresses and labels can be assigned within a scope and be deleted on scope closure. The format of a label pool is: "pool start-end, start-end" and labels can then be allocated from that range by ' **STRUCT** - -Hierarchical data structures (dot separated sub structures) - -Structs helps define complex data types, there are two basic types to define struct members, and as long as a struct is declared it can be used as a member type of another struct. - -The result of a struct is that each member is an offset from the start of the data block in memory. Each substruct is referenced by separating the struct names with dots. - -Example: - -``` -struct MyStruct { - byte count - word pointer -} - -struct TwoThings { - MyStruct thing_one - MyStruct thing_two -} - -struct Mixed { - word banana - TwoThings things -} - -Eval Mixed.things -Eval Mixed.things.thing_two -Eval Mixed.things.thing_two.pointer -Eval Mixed.things.thing_one.count -``` - -results in the output: - -``` -EVAL(16): "Mixed.things" = $2 -EVAL(27): "Mixed.things.thing_two" = $5 -EVAL(28): "Mixed.things.thing_two.pointer" = $6 -EVAL(29): "Mixed.things.thing_one.count" = $2 -``` - -**REPT** - -Repeat a scoped block of code a number of times. The syntax is rept \ { \ }. - -Example: - -``` -columns = 40 -rows = 25 -screen_col = $400 -height_buf = $1000 -rept columns { - screen_addr = screen_col - ldx height_buf - dest = screen_addr - remainder = 3 - rept (rows+remainder)/4 { - stx dest - dest = dest + 4*40 - } - rept 3 { - inx - remainder = remainder-1 - screen_addr = screen_addr + 40 - dest = screen_addr - rept (rows+remainder)/4 { - stx dest - dest = dest + 4*40 - } - } - screen_col = screen_col+1 - height_buf = height_buf+1 -} -``` - -## Expression syntax - -Expressions contain values, such as labels or raw numbers and operators including +, -, \*, /, & (and), | (or), ^ (eor), << (shift left), >> (shift right) similar to how expressions work in C. Parenthesis are supported for managing order of operations where C style precedence needs to be overrided. In addition there are some special characters supported: - -* \*: Current address (PC). This conflicts with the use of \* as multiply so multiply will be interpreted only after a value or right parenthesis -* <: If less than is not follwed by another '<' in an expression this evaluates to the low byte of a value (and $ff) -* >: If greater than is not followed by another '>' in an expression this evaluates to the high byte of a value (>>8) -* !: Start of scope (use like an address label in expression) -* %: First address after scope (use like an address label in expression) -* $: Preceeds hexadecimal value -* %: If immediately followed by '0' or '1' this is a binary value and not scope closure address - -Example: - -``` -lda #(((>SCREEN_MATRIX)&$3c)*4)+8 -sta $d018 -``` - -## Macros - -A macro can be defined by the using the directive macro and includes the line within the following scope: - -Example: -``` -macro ShiftLeftA(Source) { - rol Source - rol A -} -``` - -The macro will be instantiated anytime the macro name is encountered: -``` -lda #0 -ShiftLeftA($a0) -``` - -The parameter field is optional for both the macro declaration and instantiation, if there is a parameter in the declaration but not in the instantiation the parameter will be removed from the macro. If there are no parameters in the declaration the parenthesis can be omitted and will be slightly more efficient to assemble, as in: - -``` -.macro GetBit { - asl - bne % - jsr GetByte -} -``` - -Currently macros with parameters use search and replace without checking if the parameter is a whole word, the plan is to fix this. - -## Scopes - -Scopes are lines inbetween '{' and '}' including macros. The purpose of scopes is to reduce the need for local labels and the scopes nest just like C code to support function level and loops and inner loop scoping. '!' is a label that is the first address of the scope and '%' the first address after the scope. - -This means you can write -``` -{ - lda #0 - ldx #8 - { - sta Label,x - dex - bpl ! - } -} -``` -to construct a loop without adding a label. - -##Examples - -Using scoping to avoid local labels - -``` -; set zpTextPtr to a memory location with text -; return: y is the offset to the first space. -; (y==0 means either first is space or not found.) -FindFirstSpace - ldy #0 - { - lda (zpTextPtr),y - cmp #$20 - beq % ; found, exit - iny - bne ! ; not found, keep searching - } - rts -``` - -### Development Status - -Currently the assembler is in the first public revision and while features are tested individually it is fairly certain that untested combinations of features will indicate flaws and certain features are not in a complete state. - -**TODO** -* Macro parameters should replace only whole words instead of any substring -* Add 'import' directive as a catch-all include/incbin/etc. alternative -* irp (indefinite repeat) - -**FIXED** -* [REPT](#rept) -* fixed a flaw in expressions that ignored the next operator after raw hex values if no whitespace -* expressions now handles high byte/low byte (\>, \<) as RPN tokens and not special cased. -* structs -* ifdef / if / elif / else / endif conditional code generation directives -* Label Pools added -* Bracket scoping closure ('}') cleans up local variables within that scope (better handling of local variables within macros). -* Context stack cleanup -* % in expressions is interpreted as binary value if immediately followed by 0 or 1 -* Add a const directive for labels that shouldn't be allowed to change (currently ignoring const) -* TEXT directive converts ascii to petscii (respect uppercase or lowercase petscii) (simplistic) - -Revisions: -* 2015-10-04 Added [REPT](#rept) directive -* 2015-10-04 Added [STRUCT](#struct) directive, sorted functions by grouping a bit more, bug fixes -* 2015-10-02 Cleanup hid an error (#else without #if), exit with nonzero if error was encountered -* 2015-10-02 General cleanup, wrapping [conditional assembly](#conditional) in functions -* 2015-10-01 Added [Label Pools](#pool) and conditional assembly -* 2015-09-29 Moved Asm6502 out of Struse Samples. -* 2015-09-28 First commit +# Asm6502 6502 Macro Assembler in a single c++ file using the struse single file text parsing library. Supports most syntaxes. Every assembler seems to add or change its own quirks to the 6502 syntax. This implementation aims to support all of them at once as long as there is no contradiction. To keep up with this trend Asm6502 is adding the following features to the mix: 1. Full expression evaluation everywhere values are used: [Expressions](#expressions) 2. C style scoping within '{' and '}': [Scopes](#scopes) 3. Reassignment of labels. This means there is no error if you declare the same label twice, but on the other hand you can do things like label = label + 2. 4. [Local labels](#labels) can be defined in a number of ways, such as leading period (.label) or leading at-sign (@label) or terminating dollar sign (label$). 5. [Directives](#directives) support both with and without leading period. 6. Labels don't need to end with colon, but they can. 7. No indentation required for instructions, meaning that labels can't be mnemonics, macros or directives. 8. Conditional assembly with #if/#ifdef/#else etc. 9. As far as achievable, support the syntax of other 6502 assemblers. In summary, if you are familiar with any 6502 assembler syntax you should feel at home with Asm6502. If you're familiar with C programming expressions you should be familiar with '{', '}' scoping and complex expressions. There are no hard limits on binary size so if the address exceeds $ffff it will just wrap around to $0000. I'm not sure about the best way to handle that or if it really is a problem. There is a sublime package for coding/building in Sublime Text 3 in the *sublime* subfolder. ## Prerequisite Asm6502.cpp requires struse.h which is a single file text parsing library that can be retrieved from https://github.com/Sakrac/struse. ### References * [6502 opcodes](http://www.6502.org/tutorials/6502opcodes.html) * [6502 opcode grid](http://www.llx.com/~nparker/a2/opcodes.html) * [Codebase64 CPU section](http://codebase64.org/doku.php?id=base:6502_6510_coding) ## Features * **Code** * **Comments** * **Labels** * **Directives** * **Macros** * **Expressions** ### Code Code is any valid mnemonic/opcode and addressing mode. At the moment only one opcode per line is assembled. ### Comments Comments are currently line based and both ';' and '//' are accepted as delimiters. ### Expressions Anywhere a number can be entered it can also be interpreted as a full expression, for example: ``` Get123: bytes Get1-*, Get2-*, Get3-* Get1: lda #1 rts Get2: lda #2 rts Get3: lda #3 rts ``` Would yield 3 bytes where the address of a label can be calculated by taking the address of the byte plus the value of the byte. ### Labels Labels come in two flavors: **Addresses** (PC based) or **Values** (Evaluated from an expression). An address label is simply placed somewhere in code and a value label is follwed by '**=**' and an expression. All labels are rewritable so it is fine to do things like NumInstance = NumInstance+1. Value assignments can be prefixed with '.const' or '.label' but is not required to be prefixed by anything. *Local labels* exist inbetween *global labels* and gets discarded whenever a new global label is added. The syntax for local labels are one of: prefix with period, at-sign, exclamation mark or suffix with $, as in: **.local** or **!local** or **@local** or **local$**. Both value labels and address labels can be local labels. ``` Function: ; global label ldx #32 .local_label ; local label dex bpl .local_label rts Next_Function: ; next global label, the local label above is now erased. rts ``` ### Directives Directives are assembler commands that control the code generation but that does not generate code by itself. Some assemblers prefix directives with a period (.org instead of org) so a leading period is accepted but not required for directives. * [**ORG**](#org) (same as **PC**): Set the current compiling address. * [**LOAD**](#load) Set the load address for binary formats that support it. * [**ALIGN**](#align) Align the address to a multiple by filling with 0s * [**MACRO**](#macro) Declare a macro * [**EVAL**](#eval) Log an expression during assembly. * [**BYTES**](#bytes) Insert comma separated bytes at this address (same as **BYTE** or **DC.B**) * [**WORDS**](#words) Insert comma separated 16 bit values at this address (same as **WORD** or **DC.W**) * [**TEXT**](#text) Insert text at this address * [**INCLUDE**](#include) Include another source file and assemble at this address * [**INCBIN**](#incbin) Include a binary file at this address * [**CONST**](#const) Assign a value to a label and make it constant (error if reassigned with other value) * [**LABEL**](#label) Decorative directive to assign an expression to a label * [**INCSYM**](#incsym) Include a symbol file with an optional set of wanted symbols. * [**POOL**](#pool) Add a label pool for temporary address labels * [**#IF / #ELSE / #IFDEF / #ELIF / #ENDIF**](#conditional) Conditional assembly * [**STRUCT**](#struct) Hierarchical data structures (dot separated sub structures) * [**REPT**](#rept) Repeat a scoped block of code a number of times. * [**INCDIR**] Add a directory to look for binary and text include files in. **ORG** ``` org $2000 (or pc $2000) ``` Sets the current assembler address to this address **LOAD** ``` load $2000 ``` For c64 .prg files this prefixes the binary file with this address. **ALIGN** ``` align $100 ``` Add bytes of 0 up to the next address divisible by the alignment **MACRO** See the 'Macro' section below **EVAL** Example: ``` eval Current PC: * ``` Might yield the following in stdout: ``` Eval (15): Current PC : "*" = $2010 ``` When eval is encountered on a line print out "EVAL (\) \: \ = \" to stdout. This can be useful to see the size of things or debugging expressions. **BYTES** Adds the comma separated values on the current line to the assembled output, for example ``` RandomBytes: bytes NumRandomBytes { bytes 13,1,7,19,32 NumRandomBytes = * - ! } ``` **byte** or **dc.b** are also recognized. **WORDS** Adds comma separated 16 bit values similar to how **BYTES** work. **word** or **dc.w** are also recognized. **TEXT** Copies the string in quotes on the same line. The plan is to do a petscii conversion step. Use the modifier 'petscii' or 'petscii_shifted' to convert alphabetic characters to range. Example: ``` text petscii_shifted "This might work" ``` **INCLUDE** Include another source file. This should also work with .sym files to import labels from another build. The plan is for Asm6502 to export .sym files as well. Example: ``` include "wizfx.s" ``` **INCBIN** Include binary data from a file, this inserts the binary data at the current address. Example: ``` incbin "wizfx.gfx" ``` **CONST** Prefix a label assignment with 'const' or '.const' to cause an error if the label gets reassigned. ``` const zpData = $fe ``` **LABEL** Decorative directive to assign an expression to a label, label assignments are followed by '=' and an expression. These two assignments do the same thing (with different values): ``` label zpDest = $fc zpDest = $fa ``` **INCSYM** Include a symbol file with an optional set of wanted symbols. Open a symbol file and extract a set of symbols, or all symbols if no set was specified. Local labels will be discarded if possible. ``` incsym Part1_Init, Part1_Update, Part1_Exit "part1.sym" ``` **POOL** Add a label pool for temporary address labels. This is similar to how stack frame variables are assigned in C. A label pool is a mini stack of addresses that can be assigned as temporary labels with a scope ('{' and '}'). This can be handy for large functions trying to minimize use of zero page addresses, the function can declare a range (or set of ranges) of available zero page addresses and labels can be assigned within a scope and be deleted on scope closure. The format of a label pool is: "pool start-end, start-end" and labels can then be allocated from that range by ' **STRUCT** Hierarchical data structures (dot separated sub structures) Structs helps define complex data types, there are two basic types to define struct members, and as long as a struct is declared it can be used as a member type of another struct. The result of a struct is that each member is an offset from the start of the data block in memory. Each substruct is referenced by separating the struct names with dots. Example: ``` struct MyStruct { byte count word pointer } struct TwoThings { MyStruct thing_one MyStruct thing_two } struct Mixed { word banana TwoThings things } Eval Mixed.things Eval Mixed.things.thing_two Eval Mixed.things.thing_two.pointer Eval Mixed.things.thing_one.count ``` results in the output: ``` EVAL(16): "Mixed.things" = $2 EVAL(27): "Mixed.things.thing_two" = $5 EVAL(28): "Mixed.things.thing_two.pointer" = $6 EVAL(29): "Mixed.things.thing_one.count" = $2 ``` **REPT** Repeat a scoped block of code a number of times. The syntax is rept \ { \ }. Example: ``` columns = 40 rows = 25 screen_col = $400 height_buf = $1000 rept columns { screen_addr = screen_col ldx height_buf dest = screen_addr remainder = 3 rept (rows+remainder)/4 { stx dest dest = dest + 4*40 } rept 3 { inx remainder = remainder-1 screen_addr = screen_addr + 40 dest = screen_addr rept (rows+remainder)/4 { stx dest dest = dest + 4*40 } } screen_col = screen_col+1 height_buf = height_buf+1 } ``` ## Command Line Options Typical command line: ``` Asm6502 [-DLabel] [-iIncDir] [-sym dest.sym] [-vice dest.vs] ``` **Usage** Asm6502 [options] filename.s code.prg * -i\: Add include path * -D\[=\]: Define a label with an optional value (otherwise 1) * -c64: Include load address * -bin: Raw binary * -sym \: vice/kick asm symbol file * -vice \: export a vice symbol file ## Expression syntax Expressions contain values, such as labels or raw numbers and operators including +, -, \*, /, & (and), | (or), ^ (eor), << (shift left), >> (shift right) similar to how expressions work in C. Parenthesis are supported for managing order of operations where C style precedence needs to be overrided. In addition there are some special characters supported: * \*: Current address (PC). This conflicts with the use of \* as multiply so multiply will be interpreted only after a value or right parenthesis * <: If less than is not follwed by another '<' in an expression this evaluates to the low byte of a value (and $ff) * >: If greater than is not followed by another '>' in an expression this evaluates to the high byte of a value (>>8) * !: Start of scope (use like an address label in expression) * %: First address after scope (use like an address label in expression) * $: Preceeds hexadecimal value * %: If immediately followed by '0' or '1' this is a binary value and not scope closure address Example: ``` lda #(((>SCREEN_MATRIX)&$3c)*4)+8 sta $d018 ``` ## Macros A macro can be defined by the using the directive macro and includes the line within the following scope: Example: ``` macro ShiftLeftA(Source) { rol Source rol A } ``` The macro will be instantiated anytime the macro name is encountered: ``` lda #0 ShiftLeftA($a0) ``` The parameter field is optional for both the macro declaration and instantiation, if there is a parameter in the declaration but not in the instantiation the parameter will be removed from the macro. If there are no parameters in the declaration the parenthesis can be omitted and will be slightly more efficient to assemble, as in: ``` .macro GetBit { asl bne % jsr GetByte } ``` Currently macros with parameters use search and replace without checking if the parameter is a whole word, the plan is to fix this. ## Scopes Scopes are lines inbetween '{' and '}' including macros. The purpose of scopes is to reduce the need for local labels and the scopes nest just like C code to support function level and loops and inner loop scoping. '!' is a label that is the first address of the scope and '%' the first address after the scope. This means you can write ``` { lda #0 ldx #8 { sta Label,x dex bpl ! } } ``` to construct a loop without adding a label. ##Examples Using scoping to avoid local labels ``` ; set zpTextPtr to a memory location with text ; return: y is the offset to the first space. ; (y==0 means either first is space or not found.) FindFirstSpace ldy #0 { lda (zpTextPtr),y cmp #$20 beq % ; found, exit iny bne ! ; not found, keep searching } rts ``` ### Development Status Currently the assembler is in the first public revision and while features are tested individually it is fairly certain that untested combinations of features will indicate flaws and certain features are not in a complete state. **TODO** * Macro parameters should replace only whole words instead of any substring * Add 'import' directive as a catch-all include/incbin/etc. alternative * irp (indefinite repeat) **FIXED** * [REPT](#rept) * fixed a flaw in expressions that ignored the next operator after raw hex values if no whitespace * expressions now handles high byte/low byte (\>, \<) as RPN tokens and not special cased. * structs * ifdef / if / elif / else / endif conditional code generation directives * Label Pools added * Bracket scoping closure ('}') cleans up local variables within that scope (better handling of local variables within macros). * Context stack cleanup * % in expressions is interpreted as binary value if immediately followed by 0 or 1 * Add a const directive for labels that shouldn't be allowed to change (currently ignoring const) * TEXT directive converts ascii to petscii (respect uppercase or lowercase petscii) (simplistic) Revisions: * 2015-10-05 Added INCDIR, some command line options (-D, -i, -vice) * 2015-10-04 Added [REPT](#rept) directive * 2015-10-04 Added [STRUCT](#struct) directive, sorted functions by grouping a bit more, bug fixes * 2015-10-02 Cleanup hid an error (#else without #if), exit with nonzero if error was encountered * 2015-10-02 General cleanup, wrapping [conditional assembly](#conditional) in functions * 2015-10-01 Added [Label Pools](#pool) and conditional assembly * 2015-09-29 Moved Asm6502 out of Struse Samples. * 2015-09-28 First commit \ No newline at end of file diff --git a/asm6502.cpp b/asm6502.cpp index 267ce73..eda43e1 100644 --- a/asm6502.cpp +++ b/asm6502.cpp @@ -173,6 +173,7 @@ enum AssemblerDirective { AD_ENDIF, // #ENDIF: End a block of #IF/#IFDEF AD_STRUCT, // STRUCT: Declare a set of labels offset from a base address AD_REPT, // REPT: Repeat the assembly of the bracketed code a number of times + AD_INCDIR, // INCDIR: Add a folder to search for include files }; // Operators are either instructions or directives @@ -288,44 +289,6 @@ static const strref str_const("const"); static const strref struct_byte("byte"); static const strref struct_word("word"); -// Read in text data (main source, include, etc.) -char* LoadText(strref filename, size_t &size) { - strown<512> file(filename); - if (FILE *f = fopen(file.c_str(), "r")) { - fseek(f, 0, SEEK_END); - size_t _size = ftell(f); - fseek(f, 0, SEEK_SET); - if (char *buf = (char*)calloc(_size, 1)) { - fread(buf, 1, _size, f); - fclose(f); - size = _size; - return buf; - } - fclose(f); - } - size = 0; - return nullptr; -} - -// Read in binary data (incbin) -char* LoadBinary(strref filename, size_t &size) { - strown<512> file(filename); - if (FILE *f = fopen(file.c_str(), "rb")) { - fseek(f, 0, SEEK_END); - size_t _size = ftell(f); - fseek(f, 0, SEEK_SET); - if (char *buf = (char*)malloc(_size)) { - fread(buf, _size, 1, f); - fclose(f); - size = _size; - return buf; - } - fclose(f); - } - size = 0; - return nullptr; -} - // Binary search over an array of unsigned integers, may contain multiple instances of same key unsigned int FindLabelIndex(unsigned int hash, unsigned int *table, unsigned int count) { @@ -553,6 +516,7 @@ public: std::vector localLabels; std::vector loadedData; // free when assembler is completed std::vector structMembers; // labelStructs refer to sets of structMembers + std::vector includePaths; strovl symbols; // for building a symbol output file // context for macros / include files @@ -644,6 +608,11 @@ public: // Conditional statement evaluation (A==B? A?) StatusCode EvalStatement(strref line, bool &result); + + // Add include folder + void AddIncludeFolder(strref path); + char* LoadText(strref filename, size_t &size); + char* LoadBinary(strref filename, size_t &size); // constructor Asm() : output(nullptr) { @@ -683,6 +652,65 @@ void Asm::Cleanup() { errorEncountered = false; } +// Read in text data (main source, include, etc.) +char* Asm::LoadText(strref filename, size_t &size) { + strown<512> file(filename); + std::vector::iterator i = includePaths.begin(); + for(;;) { + if (FILE *f = fopen(file.c_str(), "r")) { + fseek(f, 0, SEEK_END); + size_t _size = ftell(f); + fseek(f, 0, SEEK_SET); + if (char *buf = (char*)calloc(_size, 1)) { + fread(buf, 1, _size, f); + fclose(f); + size = _size; + return buf; + } + fclose(f); + } + if (i==includePaths.end()) + break; + file.copy(*i); + if (file.get_last()!='/' && file.get_last()!='\\') + file.append('/'); + file.append(filename); + ++i; + } + size = 0; + return nullptr; +} + +// Read in binary data (incbin) +char* Asm::LoadBinary(strref filename, size_t &size) { + strown<512> file(filename); + std::vector::iterator i = includePaths.begin(); + for(;;) { + if (FILE *f = fopen(file.c_str(), "rb")) { + fseek(f, 0, SEEK_END); + size_t _size = ftell(f); + fseek(f, 0, SEEK_SET); + if (char *buf = (char*)malloc(_size)) { + fread(buf, _size, 1, f); + fclose(f); + size = _size; + return buf; + } + fclose(f); + } + if (i==includePaths.end()) + break; + file.copy(*i); + if (file.get_last()!='/' && file.get_last()!='\\') + file.append('/'); + file.append(filename); + ++i; + } + size = 0; + return nullptr; +} + + // Make sure there is room to assemble in void Asm::CheckOutputCapacity(unsigned int addSize) { size_t currSize = curr - output; @@ -1682,8 +1710,19 @@ StatusCode Asm::EvalStatement(strref line, bool &result) return STATUS_OK; } - - +// Add a folder for including files +void Asm::AddIncludeFolder(strref path) +{ + if (!path) + return; + for (std::vector::const_iterator i=includePaths.begin(); i!=includePaths.end(); ++i) { + if (path.same_str(*i)) + return; + } + if (includePaths.size()==includePaths.capacity()) + includePaths.reserve(includePaths.size() + 16); + includePaths.push_back(path); +} // unique key binary search int LookupOpCodeIndex(unsigned int hash, OP_ID *lookup, int count) @@ -1734,6 +1773,7 @@ DirectiveName aDirectiveNames[] { { "#ENDIF", AD_ENDIF }, { "STRUCT", AD_STRUCT }, { "REPT", AD_REPT }, + { "INCDIR", AD_INCDIR }, }; static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]); @@ -2057,6 +2097,9 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc } break; } + case AD_INCDIR: + AddIncludeFolder(line.between('"', '"')); + break; } return error; } @@ -2539,21 +2582,36 @@ void Asm::Assemble(strref source, strref filename) } } + int main(int argc, char **argv) { int return_value = 0; bool c64 = true; + Asm assembler; - const char* source_filename=nullptr, *binary_out_name=nullptr; - const char* sym_file=nullptr; + const char *source_filename=nullptr, *binary_out_name=nullptr; + const char *sym_file=nullptr, *vs_file=nullptr; for (int a=1; a