1
0
mirror of https://github.com/ksherlock/x65.git synced 2024-12-28 19:32:25 +00:00

Merge pull request #1 from Sakrac/master

Sync with x65 master
This commit is contained in:
Jason Andersen 2020-02-22 13:53:26 -05:00 committed by GitHub
commit 1030b9004f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2152 additions and 193 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,116 @@
# Command Line Options for x65
These are the current options for controlling x65 from the command line.
## lst
-lst / -lst=(file.lst)
Generate disassembly text from result(file or stdout)
## tsl
-tsl=(file)
generate listing file in TASS style
## tl
-tl=(file)
Generate labels in TASS style
## opcodes
-opcodes / -opcodes=(file.s)
Use with -cpu=... to dump all available opcodes for that CPU (file or stdout)
## endm
-endm
macros end with endm or endmacro instead of scoped('{' - '}') and rept/repeat emds with endr instead of being scoped.
## cpu
-cpu=[6502/6502ill/65c02/65c02wdc/65816]
declare CPU type, use with argument
## acc [65816]
-acc=[8/16]
set the accumulator mode for 65816 at start, default is 8 bits
## xy [65816]
-xy=8/16
set the index register mode for 65816 at start, default is 8 bits
## org
-org=$2000 or -org=4096
force assembly for first encountered non-specific address section at given address
## kickasm
-kickasm
use Kick Assembler syntax (in progress)
## merlin
-merlin
use Merlin syntax
## c64
-c64
(default) Include 2 byte load address in binary output
## a2b
-a2b
Produce an Apple II Dos 3.3 Binary
## bin
-bin
Produce raw binary
## a2p
-a2p
Produce an Apple II ProDos Binary
## a2o
-a2o
Produce an Apple II GS OS executable (relocatable)
## mrg
-mrg
Force merge all sections (use with -a2o)
## sect
-sect
display sections loaded and built
## sym
-sym (file.sym)
generate symbol file
## obj
-obj (file.x65)
Produce an object file instead of a binary for later linking
## vice
-vice (file.vs)
export a vice monitor command file (including vice symbols)
## xrefimp
-xrefimp
import directive means xref, not include/incbin and export directive means xdef, not export section.

481
docs/directives.md Normal file
View File

@ -0,0 +1,481 @@
# X65 Directives
Directives are commands that control the assembler and include controls for conditional assembly, exporting multible binary files, creating linkable object files etc.
The directives are case insensitive and can be preceeded by a dot
.rept 8 { dc.b 1<<rept }
is the same as
REPT 8 { dc.b 1<<rept }
Some directives change behavior based on [command line options](command_line_options.md), such as -endm, -xrefimp, -kickasm and -merlin.
### CPU, PROCESSOR
Assemble for this target, valid options are:
* 6502
* 6502ill (illegal opcodes)
* 65c02
* 6502wdc (adds 18 extra instructions: stp, wai, bbr0-7 & bbs0-7)
* 65816
example:
cpu 6502ill
### PC, ORG
Assemble as if loaded at this address
### LOAD
If applicable, instruct to load at this address
### EXPORT
Export this section or disable export Note that with the -xdefimp command line option this means XDEF instead and the EXPORT directive is not available.
### SECTION, SEG, SEGMENT
Enable code that will be assigned a start address during a link step, or alternatively its own load address. BSS and ZP sections will not be included in the binary output, and sections can be separately exported using the EXPORT directive.
### MERGE
Merge named sections in order listed
### LINK
Put sections with this name at this address (must be ORG / fixed address section)
### XDEF
Externally declare a symbol. When using the command line option -xdefimp EXPORT means the same thing.
### XREF
Reference an external symbol. When using the command line option -xdefimp IMPORT means the same thing.
### INCOBJ
Read in an object file saved from a previous build (that was assembled using the -obj command line option).
### ALIGN
Add to address to make it evenly divisible by this. This only works at the start of a SECTION or in the middle of a section that is assembled to a fixed address.
### MACRO, MAC
Create a macro. When used with the command line option -endm the macro ends with a ENDMACRO or ENDM directive, and if not using -endm the macro is defined within braces ( { and } ).
; standard macro usage
MACRO ldaneg(x) {
lda #-x
}
; -endm macro usage
MACRO ldaneg(x)
lda #-x
ENDM
### FUNCTION
A user function is a pre-defined one-line expression that can be used in a similar way as a macro, but instead of generating binary data it returns a single integer value.
; user defined function
FUNCTION alignto(address, alignment) (address + alignment-1) & (~alignment)
Note that functions must evaluate at the time of reference, if any symbol is not evaluated it will fail. This differs from in-place expressions that can have references that will be evaluated at a later time in assembly or at link time.
### EVAL, PRINT, ECHO
Print expression to stdout during assemble. The syntax is:
EVAL <message>: <expression>
for example
EVAL Current Address: *
test_stack = 0
eval Checking referenced function, Should be 0: .referenced(test_stack)
eval Checking defined function, Should be 1: .defined(test_stack)
### DC, DV
Declare constant / Declare Value. The directive can be specific by appending .b for byte size, .w for word size, .t for triple size or .l for long size. The default size is 1 byte.
Test:
dc.b $20, *-Test
### BYTE, BYTES
Same as dc.b
### WORD, WORDS
Same as dc.w
### LONG
Same as dc.l
### TEXT
Add text to output, the order of characters can be changed with a string symbol, for instance:
STRING FontOrder = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&*"
TEXT [FontOrder] "HELLO #1!"
### INCLUDE
Load and assemble another file at this address.
### INCBIN
Load another file and include as binary data at this address.
### INCSYM
Load symbols from a .sym file
INCSYM "Main.Sym"
Symbols can also be selected by a list on the same line:
INCSYM InitMain, UpdateMain, ShutdownMain, "Main.Sym"
### INCDIR
Add a folder to search for include files.
### IMPORT
Generic version of INCLUDE, INCBIN with custom arguments
; include a raw binary file
IMPORT "data.bin"
IMPORT binary "data.bin"
; include a source file
IMPORT source "defines.i"
; include a binary C64 file omitting the load address (first 2 bytes)
IMPORT c64 "main.prg"
; include a text file
IMPORT text "text.txt"
IMPORT text petscii "petscii.txt"
IMPORT text petscii_shifted "petscii.txt"
IMPORT text <string symbol> "custom.txt" ; see TEXT directive
Note that if the command line argument -xdefimp is used then IMPORT is equivalent to XREF instead.
### CONST
Declare a symbol as const, assgning it again will cause an error.
CONST VICBank = $4000
The constness of a symbol can be tested with the IFCONST directive or the CONST() eval function.
### LABEL
Optional directive create a mutable label, a way to specify non-CONST. It has no actual function.
### STRING
Declare a string symbol. Strings are a little bit limited but can be used for ordering characters in a TEXT declaration, or it can be used as assembler source.
; Some custom ordered text
TEXT [FontOrder] "MAKE IT SO!"
; Macro for (x=start; x<end; x++)
macro for.x Start, End {
ldx #Start
if Start < End
string _ForEnd = "inx\ncpx #End\nbne _ForLoop"
elif Start > End
{
if (-1 == End) & (Start<129)
string _ForEnd = "dex\nbpl _ForLoop"
else
string _ForEnd = "dex\ncpx #End\nbne _ForLoop"
endif
}
else
string _ForEnd = ""
endif
_ForLoop
}
macro forend {
_ForEnd ; _ForEnd defined by a variation of the for macro
undef _ForEnd
undef _ForLoop
}
for.x(5, 1)
lda buf1,x
sta buf2,x
forend
### UNDEF
Remove a symbol
like_bananas = 1
UNDEF like_bananas
### LABPOOL, POOL
Create a pool of addresses to assign as labels dynamically. This acts as a linear stack allocator for temporary storage and is deallocated when the scope ends if declared as a local symbol.
Pools can be defined as part of a larger pool.
pool zpGlobal $40-$f8 ; all zero page usage
zpGlobal pool zpLocal 16 ; temporary storage for regular functions
zpGlobal pool zpUtility 16 ; temporary storage for utility functions
zpGlobal pool zpInterrupt 8 ; temporary storage for interrupts
zpGlobal pool zpBuffer 64 ; per module storage
Allocate from a pool by using the pool name
zpBuffer zpIntroTimer.w ; frame counter, 2 bytes
zpBuffer zpScrollChar.8 ; 8 bytes of rol char for scroll
{
zpLocal .zpSrc.w ; 2 bytes source address
zpLocal .zpDst.w ; 2 bytes dest address
..
} ; at this point .zpSrc and .zpDst are deallocated and can be reused by other code.
{
zpLocal .zpCount ; 1 byte, same address as .zpSrc used above
}
### IF
Begin conditional code. Whatever lines follow will be assembled only if the expression following the IF evaluates to a non-zero value, the conditional block ends with ELSE, ELSEIF or ENDIF.
conditional_code = 1
IF conditional_code
... ; this will be assembled because conditional_code is not zero
ENDIF
### IFDEF, IFNDEF
Similar to IF but only takes one symbol and the following lines will be assembled only if the symbol was defined previously (IFDEF) or not defined previously (IFNDEF)
defined_symbol = 0
IFDEF defined_symbol
... ; this will be assembled because defined_symbol exists
ENDIF
### IFCONST
Similar to IF but like IFDEF only takes one symbol and the following lines will be assembled if the symbol is CONST. The symbol should be defined prior to testing it.
CONST() is also an Eval Function that can be used to form more complex expressions using IF. IFCONST is equivalent to IF CONST(<symbol>)
### IFBLANK, IFNBLANK
Checks if the argument exists, mostly for use in macros to test if an argument exists.
BLANK() is also an Eval Function, IFBLANK is equivalent to IF BLANK(...)
### ELSE
Requires a prior IF, the following line will be assembled only if the prior conditional block was not assembled. ELSE must be terminated by an ENDIF
IF 1
lda #0
ELSE
lda #2
ENDIF
### ELIF
Requires a prior IF and allows another expression check before ending the conditional blocks
IFDEF monkey
lda #monkey_value
ELIF DEFINED(zebra)
lda #zebra_value
ELSE
lda #human_value
ENDIF
### ENDIF
Terminated a conditional segment of blocks.
### STRUCT
Declare a set of labels offset from a base address.
Example:
STRUCT ArtSet {
word ArtTiles
word ArtColors
word ArtMasks
byte bgColor
}
Members of the structure can be referenced by the struct name dot member name:
lda ArtSetData + ArtSet.bgColor
ArtSetData:
ds SIZEOF(ArtSet)
### ENUM
Declare a set of incremental labels. Values can either be assigned or one more than the previous. The default first value is 0.
enum PlayerIndex {
None = -1,
One,
Two
Three,
Four,
Count ; there are this many players
}
Enum values can be referenced by enum name dot value name:
ldx #PlayerIndex.One
{
inx
cpx #PlayerIndex.Count
bcc !
}
lda #PlayerIndex.Four
### REPT, REPEAT
Repeats the code within { and } following the REPT directive and counter. Within the REPT code the symbol REPT has the current iteration count, starting at 0.
const words = 10
.rept words * 2 { dc.b rept / 2 }
If the command line option -endm is used then REPT uses ENDR instead of the braced scope so the equivalent to the above would be
.rept words * 2
dc.b rept / 2
.endr
### A16, A8, XY16, XY8, I16, I8
Specific to 65816 assembly, controls the current accumulator and index register width (8 or 16 bits). Different assemblers use different names so various alternatives are allowed.
### DUMMY, DUMMY_END
Creates a dummy section between DUMMY and DUMMY_END directives.
### DS, RES
Define "section", Reserve. Reserves a number of bytes at the current address. The first argument is the number of bytes and the second argument is optional and is the byte to fill with. The main purpose is to reserve space in a BSS or ZP section.
### SCOPE, ENDSCOPE
A specialized version of a scope, does the same this as a brace scope (code between { and }) but additionally marks all labels defined within as local. An unimplemented feature is that the scope can be named and then labels defined can be accessed outside the scope as
<scope name>::<label> or <scope name>.label (TODO!)
### PUSH, PULL
Creates a stack for a mutable symbol so that it can temporarily be redefined and then restored.
do_thing = 1
IF do_thing
.. ; do thing
ENDIF
PUSH do_thing
do_thing = 0
IF do_thing
.. ; do not do thing
ENDIF
PULL do_thing
IF do_thing
.. ; restored symbol, let's do thing again!
ENDIF
### ABORT, ERR
Stops assembly with an error if encountered and prints the rest of the line to the output.
---
# Merlin Specific Directives
### MX
### STR
### DA
### DN
### ASC
### PUT
### DDB
### DB
### DFB
### HEX
### DO
### FIN
### EJECT
### OBJ
### TR
### END
### REL
### USR
### DUM
### DEND
### LST, LSTDO
### LUP
### SAV, DSK
### LNK
### XC
### ENT
### EXT
### ADR
### ADRL
### CYC

175
docs/errors.md Normal file
View File

@ -0,0 +1,175 @@
# List of x65 error messages
## Undefined code
Could not recognize code at this point in the file
## Unexpected character in expression
A character in an expression has confused the assembler evaluator
## Too many values in expression
There is a limit to the number of values encountered in a single expression, feel free to change that number if you must.
## Too many operators in expression
There is a limit to the number of operators encountered in a single expression, this can also be modified as needed.
## Unbalanced right parenthesis in expression
A right parenthesis without a corresponding left parenthesis was encountered in the expression
## Expression operation
The expression evaluator has confused itself with an unrecognized operator.
## Expression missing values
Not enough values to complete an expression operator
## Instruction can not be zero page
An attempt to force a zero page command that does not support it was foiled by the assembler.
## Invalid addressing mode for instruction
Indeed!
## Internal label organization mishap
Internal error
## Bad addressing mode
Don't be bad
## Unexpected character in addressing mode
What gives?
## Unexpected label assignment format
Equal sign or EQU is desired for assigning const or mutable (LABEL) labels.
## Changing value of label that is constant
You declared that you would not change your mind but you did. I can't deal with it.
## Out of labels in pool
A label pool was declared at a certain size that has now been exceeded.
## Internal label pool release confusion
Internal error
## Label pool range evaluation failed
Could not determine the range for the label pool at the current line of assembly
## Label pool was redeclared within its scope
No recursive pool dipping please.
## Pool label already defined
Once is enough
## Struct already defined
Don't repeat yourself
## Referenced struct not found
But specify it at least once.
## Declare constant type not recognized (dc.?)
Specify word size using something that can be understood
## rept count expression could not be evaluated
The count needs to be evaluated at the current line of assembly
## hex must be followed by an even number of hex numbers
## DS directive failed to evaluate immediately
The count needs to be evaluated at the current line of assembly
## File is not a valid x65 object file
A file that was referenced with an INCOBJ directive was not recognized as a valid x65 object file
## Failed to read include file
## Using symbol PULL without first using a PUSH
Withdrawing beyond your deposits amounts to robbery.
## User invoked error
An ABORT or ERR directive was assembled
## Errors after this point will stop execution
This is a placeholder error message
## Branch is out of range
Max branch distance was exceeded at this point in assembly
## Function declaration is missing name or expression
A FUNCTION directive requires a name, open/close parenthesis and an expression. Parameters within the parenthesis is optional.
## Function could not resolve the expression
The expression could not be evaluated at this point in assembly
## Expression evaluateion recursion too deep
## Target address must evaluate immediately for this operation
## Scoping is too deep
## Unbalanced scope closure
## Unexpected macro formatting
## Align must evaluate immediately
## Out of memory for macro expansion
Your memory is not enough
## Problem with macro argument
## Conditional could not be resolved
## #endif encountered outside conditional block
ENDIF directive without an IF or equivalent
## #else or #elif outside conditional block
ELSE or ELIF directive without an IF or equivalent
## Struct can not be assembled as is
## Enum can not be assembled as is
## Conditional assembly (#if/#ifdef) was not terminated in file or macro
an IF or equivalent does not have a matching ENDIF directive
## rept is missing a scope ('{ ... }')
You want me to repeat what exactly?
## Link can only be used in a fixed address section
## Link can not be used in dummy sections
## Can not process this line
## Unexpected target offset for reloc or late evaluation
## CPU is not supported
## Can't append sections
## Zero page / Direct page section out of range
## Attempting to assign an address to a non-existent section
## Attempting to assign an address to a fixed address section
## Can not link a zero page section with a non-zp section
## Out of memory while building
## Can not write to file
## Assembly aborted
## Condition too deeply nested
There is a limit to the number of IFs within IFs.

48
docs/eval_functions.md Normal file
View File

@ -0,0 +1,48 @@
# Eval Functions
Eval Functions are used like symbols in expressions but are always followed by parenthesis with optional arguments.
### DEFINED, DEF
.if .def(symbol)
.endif
Evaluates to 1 if the symbol has been defined or 0 if it has not been encountered to this point in the current assembly.
### REFERENCED
.if .referenced(symbol)
.endif
Evaluates to 1 if the symbol has been referenced in the current assembly, the symbol should be defined at this point.
### BLANK
.if .blank()
.endif
Evaluates to 1 if the contents within the parenthesis is empty, primarily for use within macros.
### CONST
if .const(symbol)
.endif
Evaluates to 1 if the symbol has been declared CONST, the symbol should be defined at this point.
### SIZEOF
STRUCT Module {
word Init
word Update
word Shutdown
}
ds SIZEOF( Module )
Returns the byte size of a given struct.
### TRIGSIN
Not implemented, experimental math feature, currently returns 0.

164
docs/macro_samples.md Normal file
View File

@ -0,0 +1,164 @@
# x65macro.i
This is a file under macros and is intended as an example to look at for understanding macro features, it is not super tested for correctness. This information is included in the header file itself but to ease reading copied here. The macros folder also has [more detailed documentation](../macros/README.MD).
## Suffix definition
The letters after the period has the following meanings:
- b: byte
- w: word (2 bytes)
- t: triple (3 bytes)
- l: long (4 bytes)
- n: number of bytes in value
- c: copy result to target
- i: immediate, for example add a value to the contents of an address
- x: use the x register for operation as a counter or an offset
- y: use the y register for operation
- r: relative; ry=(zp),y
- a: use the contents of an address for operation (16 bits)
- s: custom step size (instead of +1 or -1) for loops
- p: positive
- m: negative
- o: use label pool for counter
## operations
The base operations provided by these macros are:
- set: Assign a value to the contents of an address
- move: Move the contents of an address to another address
- add: addition
- sub: subtraction
- asrm: arithmetic shift right
- aslm: arithmetic shift left
- neg: negate a number
- abs: make a number positive
- copy: copy memory from one location to another
- for: iterate between two numbers with optional step size
- mnop: insert multiple nop at this point
set.b / .w / .t / .l Value, Target
- set the contents of an 1-4 byte location to a value
- uses accumulator
move.b / .w / .t / .l / .n Src,Trg
- copy 1-4 (or n) bytes from Src location to Trg location
- uses accumulator
asrm.n Target, Size
- shift a signed multi byte number right
- uses accumulator
asrm.nx Target, Size
- shift a signed multi byte number right offset by the x register
- no registers touched
aslm.n Target, Size
- shift a multi byte number left
- no registers touched
aslm.nx Target, Size
- shift a multi byte number left offset by the x register
- no registers changed
neg.cn Source, Target, Size
- negate and copy a multi byte number
- uses accumulator
neg.n Target, Size
- negate a number in place
- uses accumulator
abs.n Trg, Size
- make a number absolute
- uses accumulator
neg.nx Trg, Size
- negate a number in place offset by the x register
- uses accumulator
add.n Address1, Address2, Target, Bytes
- add contents of two memory locations into a target lcoation
- uses accumulator
sub.n Address1, Address2, Target, Bytes
- Target = Address1 - Address2
- uses accumulator
add.ni Address, Value, Target, Bytes
- add a fixed value to a memory location into a target
- uses accumulator
sub.ni Address, Value, Target, Bytes
- Target = Address - Value
- uses accumulator
add.wi Address, Value, Target
- Subtract 16 bit Value from contents of Address and store at Target
- uses accumulator
sub.wi Address1, Address2, Target
- add contents of two 16 bit addresses into a target 16 bit location
- uses accumulator
mnop Count
- add Count nops
copy.x Source, Target, Size
- copy up to 256 bytes using the x register as a counter
- uses accumulator and x register
copy.y Source, Target, Size
- copy up to 256 bytes using the y register as a counter
- uses accumulator and y register
copy.ry zpSrcPtr,zpTrgPtr,Size
- copy a fixed length buffer using relative zp y indexing
- size is up to a page, changing Y and A
copy.ry128 zpSrcPtr,zpTrgPtr,Size
- copy up to 128 bytes using the y register
copy.o Src,Trg,Size,PoolZP
- copy more than 256 bytes using zero page label pool addresses
- uses accumulator, x and y register
copy.a Src,Trg,Size
- copy more than 256 bytes using absolute indexed in a loop
- uses accumulator, x and y register
copy.zp Src,Trg,Size,zpTmp1,zpTmp2
- copy more than 256 bytes using two pairs of zero page values
- uses accumulator, x and y register
for.x Start, End
- iterate using the x register from Start to End, End is not inclusive
so to iterate from 31 to 0 use for.x 31, -1
- uses x register
- end for loop with forend macro
for.y Start, End
- same as for.x but with the y register
- uses y register
- end for loop with forend macro
for.w Start, End, Counter
- for loop for 16 bit counter
- uses accumulator
- end for loop with forend macro
for.ws Start, End, Counter, Step
- for loop for 16 bit counter with a step value
- uses accumulator
- end for loop with forend macro
for.wsp Start, End, Counter, Step {
- for (word Counter=start; Counter<end; Counter += Step), Step>0
- uses accumulator
for.wsm Start, End, Counter, Step {
- for (word Counter=start; Counter<end; Counter += Step), Step<0
- uses accumulator
forend
- terminates for loops

64
docs/readme.md Normal file
View File

@ -0,0 +1,64 @@
# x65 Assembler
x65 is an open source 6502 series assembler that supports object files,
linking, fixed address assembling and a relocatable executable.
Assemblers have existed for a long time and what they do is well documented,
x65 tries to accomodate most expectations of syntax from Kick Assembler (a
Java 6502 assembler) to Merlin (an Apple II assembler).
For debugging, dump_x65 is a tool that will show all content of x65 object
files, and x65dsasm is a disassembler intended to review the assembled
result.
## Noteworthy features:
* Code with sections, object files and linking or single file fixed
address, or mix it up with fixed address sections in object files.
* Assembler listing with cycle counting for code review.
* Export multiple binaries with a single link operation.
* C style scoping within '{' and '}' with local and pool labels
respecting scopes.
* Conditional assembly with if/ifdef/else etc.
* Assembler directives representing a variety of features.
* Local labels can be defined in a number of ways, such as leading
period (.label) or leading at-sign (@label) or terminating
dollar sign (label$).
* String Symbols system allows building user expressions and macros
during assembly.
* Reassignment of symbols and labels by default.
* No indentation required for instructions, meaning that labels can't
be mnemonics, macros or directives.
* Supporting the syntax of other 6502 assemblers (Merlin syntax
requires command line argument, -endm adds support for sources
using macro/endmacro and repeat/endrepeat combos rather
than scoeps).
* Apple II GS executable output.
## Command Line Options
Controls the assembler for the entire file
See [Command Line Options](command_line_options.md)
## Directives
Controls the assembler on a line basis
See [Directives](directives.md)
## Eval Functions
Functions that returns values for use in expressions.
See [Eval Functions](eval_functions.md)
## Macro examples
Some info about the included example x65macro.i file.
See [Macro Samples](macro_samples.md)
## List of Errors
See [Error List](errors.md)

View File

@ -635,7 +635,7 @@ _ForLoop
macro for.y Start, End {
ldx #Start
if Start < End
string _ForEnd = "iny\ncpx #End\nbne _ForLoop"
string _ForEnd = "iny\ncpy #End\nbne _ForLoop"
elif Start > End
{
if (-1 == End) & (Start<129)

View File

@ -23,32 +23,32 @@
<ProjectGuid>{57EFF4A4-7BF2-43F0-AD62-A79092DA67D1}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>dump_x65</RootNamespace>
<WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>

View File

@ -29,26 +29,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>

168
struse.h
View File

@ -35,7 +35,7 @@ Add this #define to *one* C++ file before #include "struse.h" to create the impl
#ifndef __STRUSE_H__
#define __STRUSE_H__
#include <inttypes.h> // uint8_t etc.
#include <inttypes.h> // uint32_t etc.
#include <string.h> // memcpy, memmove
#include <stdio.h> // printf, vsnprintf
#include <stdarg.h> // va_list
@ -128,12 +128,15 @@ public:
// convert hexadecimal string to unsigned integer
size_t ahextoui() const;
uint64_t ahextou64() const;
uint64_t ahextoui_skip();
size_t ahextoui_skip();
size_t abinarytoui_skip();
// output string with newline (printf)
void writeln();
// single digit number
static char num_to_char(uint8_t num) { return num<10 ? (num+'0'):(num+'a'-10); }
// is character empty such as space, tab, linefeed etc.?
static bool is_ws(uint8_t c) { return c <= ' '; }
static bool is_ws(char c) { return (uint8_t)c <= ' '; }
@ -238,6 +241,9 @@ public:
// check if string is a valid floating point number
bool is_float_number() const { return valid() && len_float_number() == length; }
// check if matching first char and skip if match
bool grab_char( char c ) { if(length && string[0]==c) { length--; string++; return true; } return false; }
// wildcard search
strref find_wildcard(const strref wild, strl_t pos = 0, bool case_sensitive = true) const;
strref next_wildcard(const strref wild, strref prev, bool case_sensitive = true) const {
@ -282,6 +288,7 @@ public:
(str.get_len()==length || !is_alphanumeric((uint8_t)str[length])); }
bool is_prefix_case_of(const strref str) const { return prefix_len_case(str)==get_len(); }
bool is_prefix_float_number() const { return len_float_number() > 0; }
bool grab_prefix( const char* str ) { strl_t p = prefix_len( str ); if( !str[ p ] ) { skip( p ); return true; } return false; }
// suffix compare
strl_t suffix_len(const strref str) const;
@ -364,6 +371,9 @@ public:
// find any char from str or char range or char - with backslash prefix
int find_range_char_within_range(const strref range_find, const strref range_within, strl_t pos = 0) const;
// find but not within parenthesis
int find_skip_parens(char token) const;
// counts
int substr_count(const strref str) const; // count the occurrences of the argument in this string
int substr_count_bookend(const strref str, const strref bookend) const;
@ -521,6 +531,10 @@ public:
strref before_or_full(char c) const {
int o = find(c); if (o>=0) return strref(string, o); return *this; }
strref before_or_full_track_parens(char c) const {
int o = find_skip_parens(c); if (o >= 0) return strref(string, o); return *this;
}
strref before_last(char c) const {
int o = find_last(c); if (o>=0) return strref(string, o); return strref(); }
@ -575,20 +589,29 @@ public:
strref split_token_any(const strref chars);
strref split_token_trim(char c);
strref split_token_any_trim(const strref chars);
strref split_token_track_parens(char c);
strref split_token_trim_track_parens(char c);
strref split_range(const strref range, strl_t pos=0);
strref split_range_trim(const strref range, strl_t pos=0);
strref split_label();
strref split_lang();
strref split_num();
// get a snippet, previous and full current line around a position
strref get_snippet( strl_t pos );
// grab a block of text starting with (, [ or { and end with the corresponding number of ), ] or }
strref scoped_block_skip();
strref scoped_block_skip( bool quotes = false );
// scoped_block_skip with C style comments
strl_t scoped_block_comment_len();
strl_t scoped_block_utf8_comment_len();
strref scoped_block_comment_skip(bool include = false) { strref ret = split(scoped_block_comment_len()); if (!include) { ++ret; ret.clip(1); } return ret; }
strref scoped_block_utf8_comment_skip( bool include = false ) {
strref ret = split( scoped_block_utf8_comment_len() );
if( !include ) { ++ret; ret.clip( 1 ); }
return ret;
}
// check matching characters that are terminated by any character in term or ends
strl_t match_chars_str(const strref match, const strref term = strref());
@ -610,6 +633,7 @@ public:
int l = strref(string+f, length-f).find(b); if (l<0) l = 0; return strref(string+f, l); }
strref get_quote_xml() const;
strref skip_quote_xml();
int find_quoted_xml(char d) const; // returns length up to the delimiter d with xml quotation rules, or -1 if delimiter not found
int find_quoted(char d) const; // returns length up to the delimiter d with c/c++ quotation rules, or -1 if delimiter not found
@ -628,10 +652,12 @@ strl_t _strmod_append(char *string, strl_t length, strl_t cap, const char *str);
strl_t _strmod_append(char *string, strl_t length, strl_t cap, strref str);
strl_t _strmod_insert(char *string, strl_t length, strl_t cap, const strref sub, strl_t pos);
strl_t _strmod_utf8_tolower(char *string, strl_t length, strl_t cap);
strl_t _strmod_write_utf8( char *string, strl_t cap, size_t code, strl_t pos );
void _strmod_substrcopy(char *string, strl_t length, strl_t cap, strl_t src, strl_t dst, strl_t chars);
void _strmod_tolower(char *string, strl_t length);
void _strmod_toupper(char *string, strl_t length);
strl_t _strmod_format_insert(char *string, strl_t length, strl_t cap, strl_t pos, strref format, const strref *args);
strl_t _strmod_append_num(char* str, strl_t left, uint32_t num, strl_t size, uint32_t radix);
strl_t _strmod_remove(char *string, strl_t length, char a);
strl_t _strmod_remove(char *string, strl_t length, strl_t start, strl_t len);
strl_t _strmod_exchange(char *string, strl_t length, strl_t cap, strl_t start, strl_t size, const strref insert);
@ -721,6 +747,7 @@ public:
bool is_prefix_word(const strref str) const { return get_strref().is_prefix_word(str); }
bool is_prefix_case_of(const strref str) const { return get_strref().is_prefix_case_of(str); }
bool is_prefix_float_number() const { return get_strref().is_prefix_float_number(); }
bool grab_prefix( const char* str ) { return get_strref().grab_prefix( str ); }
// whole word compare (prefix match + next char is whitespace or end of string)
bool is_word(const strref str) const { return get_strref().is_word(str); }
@ -908,6 +935,11 @@ public:
void format_insert(const strref format, const strref *args, strl_t pos) {
set_len_int(_strmod_format_insert(charstr(), len(), cap(), pos, format, args)); }
strmod& append_num(uint32_t num, strl_t size, strl_t radix) {
add_len_int( _strmod_append_num( charstr() + len(), cap() - len(), num, size, radix ) );
return *this;
}
// c style sprintf (work around windows _s preference)
#ifdef _WIN32
int sprintf(const char *format, ...) { va_list args; va_start(args, format);
@ -1567,7 +1599,7 @@ size_t strref::ahextoui() const
{
const char *scan = string;
strl_t left = length;
while (*scan<=0x20 && left) {
while (left && *scan<=0x20) {
scan++;
left--;
}
@ -1624,7 +1656,7 @@ uint64_t strref::ahextou64() const
return hex;
}
// convert a hexadecimal string to an unsigned integer
uint64_t strref::ahextoui_skip()
size_t strref::ahextoui_skip()
{
const char *scan = string;
strl_t left = length;
@ -1638,8 +1670,8 @@ uint64_t strref::ahextoui_skip()
scan += 2;
left -= 2;
}
if( left > 16 ) { left = 16; }
uint64_t hex = 0;
if (left > 16) { left = 16; }
size_t hex = 0;
while (left) {
char c = *scan;
if (c>='0' && c<='9')
@ -1733,9 +1765,8 @@ int strref::count_char(char c) const
strref strref::skip_bom()
{
const uint8_t* buf = get_u();
if( length >= 3 && buf && buf[ 0 ] == 0xef && buf[ 1 ] == 0xbb && buf[ 2 ] == 0xbf )
{
return strref( string + 3, length - 3 );
if (length >= 3 && buf && buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf) {
return strref(string + 3, length - 3);
}
return *this;
}
@ -1863,6 +1894,20 @@ int strref::find_last(char c, char d) const
return -1;
}
int strref::find_skip_parens(char token) const
{
int parens = 0;
const char* scan = string;
strl_t left = length;
while (left && (parens || *scan != token)) {
if (*scan == '(') { ++parens; } else if (*scan == ')' && parens) { --parens; }
--left;
++scan;
}
if (left) { return length - left; }
return -1;
}
// compare a string with a substring case sensitive
static bool int_compare_substr_case(const char *scan, strl_t length, const char *check, strl_t chk_len)
{
@ -4046,6 +4091,28 @@ strref strref::get_quote_xml() const
return strref();
}
// if this string begins as an xml quote return that.
strref strref::skip_quote_xml()
{
char quote_char = get_first();
if( quote_char != '"' && quote_char != '\'' )
return strref();
const char *scan = string + 1;
strl_t left = length - 1;
while( left ) {
char c = *scan++;
if( c == quote_char ) {
strref ret( string + 1, length - left - 1 );
string = scan+1;
length = left-2;
return ret;
}
--left;
}
return strref();
}
// find the character d outside of a quote
int strref::find_quoted(char d) const
{
@ -4086,6 +4153,15 @@ strref strref::split_token( char c ) {
return r;
}
strref strref::split_token_track_parens(char c)
{
int t = find_skip_parens(c);
if (t < 0) t = (int)length;
strref r = strref(string, strl_t(t));
*this += t + 1;
return r;
}
strref strref::split_token_any( const strref chars )
{
strref r; int t = find_any_char_of( chars );
@ -4096,6 +4172,13 @@ strref strref::split_token_any( const strref chars )
return r;
}
strref strref::split_token_trim_track_parens(char c)
{
strref r = split_token_track_parens(c);
skip_whitespace();
r.trim_whitespace();
return r;
}
strref strref::split_token_trim( char c ) {
strref r = split_token( c );
skip_whitespace();
@ -4140,6 +4223,19 @@ strref strref::split_label() {
return r;
}
strref strref::split_num() {
skip_whitespace();
strref r( string, 0 );
while( length && *string >= '0' && *string <= '9' ) {
r.length++;
string++;
length--;
}
skip_whitespace();
return r;
}
// split string based on common programming tokens (words, quotes, scopes, numbers)
strref strref::split_lang()
{
@ -4177,18 +4273,22 @@ strref strref::get_snippet( strl_t pos )
}
// grab a block of text starting with (, [ or { and end with the corresponding number of ), ] or }
strref strref::scoped_block_skip()
strref strref::scoped_block_skip(bool quotes)
{
char scope = get_first();
if (length && (scope == '(' || scope == '[' || scope == '{')) {
char close = scope=='(' ? ')' : (scope=='[' ? ']' : '}');
const char *scan = string;
bool inQuote = false;
strl_t depth = 0;
strl_t left = length;
do {
char c = *scan++;
left--;
if (c==scope)
if( inQuote ) {
if( c == '"' ) { inQuote = false; }
} else if( quotes && c=='"' ) { inQuote = true; }
else if( c == scope )
depth++;
else if (c==close)
depth--;
@ -4236,6 +4336,31 @@ strl_t strref::scoped_block_comment_len()
return 0;
}
strl_t strref::scoped_block_utf8_comment_len()
{
strref str = *this;
size_t scope = str.pop_utf8();
if( length && ( scope == '(' || scope == '[' || scope == '{' || scope == '<' ) )
{
char close = scope == '<' ? '>' : ( scope == '(' ? ')' : ( scope == '[' ? ']' : '}' ) );
strl_t depth = 1;
do {
size_t c = str.pop_utf8();
if( c == '/' && str.get_len() && ( str[0] == '/' || str[1] == '*' ) ) {
c = str.pop_utf8();
strl_t skip = c == '/' ? str.len_next_line() : str.find_or_full( "*/" );
str += skip;
}
else if( c == scope )
depth++;
else if( c == close )
depth--;
} while( depth && str.valid() );
if( !depth )
return strl_t( str.string - string );
}
return 0;
}
// return the current line of text and move this string ahead to the next.
@ -4513,6 +4638,25 @@ strl_t _strmod_format_insert(char *string, strl_t length, strl_t cap, strl_t pos
return length;
}
strl_t _strmod_append_num( char* str, strl_t left, uint32_t num, strl_t size, uint32_t radix )
{
strl_t div = 1;
if( !size ) {
uint32_t mul = 1;
do { ++size; mul *= radix; } while( mul <= num );
}
for( strl_t n = 1; n<size; ++n ) { div *= radix; }
strl_t added = 0;
for( strl_t a = 0; a<size && left; ++a ) {
char v = (num / div) % radix + '0';
div /= radix;
*str++ = v <= '9' ? v : (v + 'a' - '0' - 10);
--left;
++added;
}
return added;
}
// remove all instances of a character from a string
strl_t _strmod_remove(char *string, strl_t length, char a)
{

View File

@ -278,7 +278,7 @@ TestOpcodes:
mvp $21,$20
mvn $21,$20
pea $2120
pei ($21)
pei $21
per $2120
rep $21
rep #$21

107
test/ca65directive.s Normal file
View File

@ -0,0 +1,107 @@
; TEST CODE FROM EXOMIZER
cpu 65816
.org $52000
.REPT 7
dc.b rept
.ENDR
eval Checking defined function, Should be 0: .defined(test_stack)
test_stack = 0
eval Checking referenced function, Should be 0: .referenced(test_stack)
eval Checking defined function, Should be 1: .defined(test_stack)
PUSH test_stack
eval Checking referenced function, Should be 1: .referenced(test_stack)
test_stack = 10
eval Push Before Pull: test_stack
PULL test_stack
eval Pull original: test_stack
eval Checking symbol is not const (0): .const(test_stack)
const ConstAddress = $1000
eval Checking symbol is const (1): .const(ConstAddress)
eval This should be blank (1): .blank()
eval This should be blank (1): .blank({})
eval This should be not be blank (0): .blank({monkeys})
.ifconst test_stack
eval Checking ifconst with non-const symbol, should not print:
.endif
.ifconst ConstAddress
eval Checking ifconst with const symbol, this should print:
.endif
struct MyStruct {
word addr
byte value
}
eval Size of MyStruct (3): .sizeof(MyStruct)
DISP_BRIGHTNESS_MASK = $f0
DISP_BLANKING_SHIFT = 7
.function inidisp(blanking, brightness) (<(((~blanking & 1) << DISP_BLANKING_SHIFT) | (brightness & ~DISP_BRIGHTNESS_MASK)))
eval Function test, should be (<(((~7&1)<<7)|(12&~$f0) = <((0<<7)|(c&f)) = $c: inidisp(7, 12)
zp_len_lo = $a7
zp_len_hi = $a8
zp_src_lo = $ae
zp_src_hi = zp_src_lo + 1
zp_bits_hi = $fc
zp_bitbuf = $fd
zp_dest_lo = zp_bitbuf + 1 ; dest addr lo
zp_dest_hi = zp_bitbuf + 2 ; dest addr hi
.MACRO mac_refill_bits
pha
jsr get_crunched_byte
rol
sta zp_bitbuf
pla
.ENDMACRO
.MACRO mac_get_bits
.SCOPE
adc #$80 ; needs c=0, affects v
asl
bpl gb_skip
gb_next:
asl zp_bitbuf
bne gb_ok
mac_refill_bits
gb_ok:
rol
bmi gb_next
gb_skip:
bvc skip
gb_get_hi:
sec
sta zp_bits_hi
jsr get_crunched_byte
skip:
.ENDSCOPE
.ENDMACRO
.ifdef UNDEFINED_SYMBOL
dc.b -1 ; should not be assembled
error 1
.else
dc.b 1 ; should be assembled
.endif
const CONSTANT = 32
.eval CONSTANT
mac_get_bits
mac_get_bits
get_crunched_byte:
rts

View File

@ -85,6 +85,16 @@ echo Merlin LUP test failed
goto exit
:merlup_pass
echo CA65 directives Test >>results\unittest.txt
..\bin\x64\x65.exe ca65directive.s -lst -endm >>results\unittest.txt
if %errorlevel% GTR 0 goto ca65_fail
rem check data here when relevant
if %errorlevel% EQU 0 goto ca65_pass
:ca65_fail
echo CA65 directives failed
goto exit
:ca65_pass
rem REVIEW MACROS!
rem echo x65macro.i Test >>results\unittest.txt
rem echo --------------- >>results\unittest.txt

990
x65.cpp

File diff suppressed because it is too large Load Diff