1
0
mirror of https://github.com/ksherlock/x65.git synced 2024-06-02 18:41:34 +00:00

Label Pools and Conditional Assembly

Awesome stuff added.
This commit is contained in:
Carl-Henrik Skårstedt 2015-10-01 23:16:36 -07:00
parent 80c3016857
commit d5edf1d593
2 changed files with 635 additions and 145 deletions

107
README.md
View File

@ -90,21 +90,22 @@ Next_Function: ; next global label, the local label above is now erased.
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** (same as **PC**): Set the current compiling address.
* **LOAD** Set the load address for binary formats that support it.
* **ALIGN** Align the address to a multiple by filling with 0s
* **MACRO** Declare a macro
* **EVAL** Log an expression during assembly.
* **BYTES** Insert comma separated bytes at this address (same as **BYTE**)
* **WORDS** Insert comma separated 16 bit values at this address (same as **WORD**)
* **TEXT** Insert text at this address
* **INCLUDE** Include another source file and assemble at this address
* **INCBIN** Include a binary file at this address
* **CONST** Assign a value to a label and make it constant (error if reassigned with other value)
* **LABEL** Decorative directive to assign an expression to a label
* **INCSYM** Include a symbol file with an optional set of wanted symbols.
* [**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**)
* [**WORDS**](#words) Insert comma separated 16 bit values at this address (same as **WORD**)
* [**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
**ORG**
<a name="org">**ORG**
```
org $2000
@ -113,7 +114,7 @@ org $2000
Sets the current assembler address to this address
**LOAD**
<a name="load">**LOAD**
```
load $2000
@ -121,7 +122,7 @@ load $2000
For c64 .prg files this prefixes the binary file with this address.
**ALIGN**
<a name="align">**ALIGN**
```
align $100
@ -129,11 +130,11 @@ align $100
Add bytes of 0 up to the next address divisible by the alignment
**MACRO**
<a name="macro">**MACRO**
See the 'Macro' section below
**EVAL**
<a name="eval">**EVAL**
Example:
```
@ -146,7 +147,7 @@ Eval (15): Current PC : "*" = $2010
When eval is encountered on a line print out "EVAL (\<line#\>) \<message\>: \<expression\> = \<evaluated expression\>" to stdout. This can be useful to see the size of things or debugging expressions.
**BYTES**
<a name="bytes">**BYTES**
Adds the comma separated values on the current line to the assembled output, for example
@ -161,11 +162,11 @@ RandomBytes:
byte is also recognized
**WORDS**
<a name="words">**WORDS**
Adds comma separated 16 bit values similar to how **BYTES** work
**TEXT**
<a name="text">**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.
@ -175,7 +176,7 @@ Example:
text petscii_shifted "This might work"
```
**INCLUDE**
<a name="include">**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.
@ -186,7 +187,7 @@ include "wizfx.s"
```
**INCBIN**
<a name="incbin">**INCBIN**
Include binary data from a file, this inserts the binary data at the current address.
@ -196,7 +197,7 @@ Example:
incbin "wizfx.gfx"
```
**CONST**
<a name="const">**CONST**
Prefix a label assignment with 'const' or '.const' to cause an error if the label gets reassigned.
@ -204,7 +205,7 @@ Prefix a label assignment with 'const' or '.const' to cause an error if the labe
const zpData = $fe
```
**LABEL**
<a name="label">**LABEL**
Decorative directive to assign an expression to a label, label assignments are followed by '=' and an expression.
@ -214,14 +215,50 @@ label zpDest = $fc
zpDest = $fa
```
**INCSYM** Include a symbol file with an optional set of wanted symbols.
<a name="incsym">**INCSYM**
Open a symbol file and extract a set of symbols, or all symbols if no set was specified.
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"
```
<a name="pool">**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 <pool name> start-end, start-end" and labels can then be allocated from that range by '<pool name> <label name>[.b][.w]' where .b means allocate one byte and .w means allocate two bytes. The label pools themselves are local to the scope they are defined in so you can have label pools that are only valid for a section of your code. Label pools works with any addresses, not just zero page addresses.
Example:
```
Function_Name: {
pool zpWork $f6-$100 ; these zero page addresses are available for temporary labels
zpWork zpTrg.w ; zpTrg will be $fe
zpWork zpSrc.w ; zpSrc will be $fc
lda #>Src
sta zpSrc
lda #<Src
sta zpSrc+1
lda #>Dest
sta zpDst
lda #<Dest
sta zpDst+1
{
zpWork zpLen ; zpLen will be $fb
lda #Length
sta zpLen
}
nop
{
zpWork zpOff ; zpOff will be $fb (shared with previous scope zpLen)
}
rts
```
## <a name="expressions">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:
@ -270,12 +307,14 @@ Scopes are lines inbetween '{' and '}' including macros. The purpose of scopes i
This means you can write
```
lda #0
ldx #8
{
sta Label,x
dex
bpl !
lda #0
ldx #8
{
sta Label,x
dex
bpl !
}
}
```
(where ; represents line breaks) to construct a loop without adding a label.
@ -305,18 +344,20 @@ FindFirstSpace
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 (such as the TEXT directive not bothering to convert ascii to petscii for example).
**TODO**
* Bracket scoping closure ('}') should clean up local variables within that scope (better handling of local variables within macros).
* Macro parameters should replace only whole words instead of any substring
* Add 'import' directive as a catch-all include/incbin/etc. alternative
* ifdef / if / elif / else / endif conditional code generation directives
* rept / irp macro helpers (repeat, indefinite repeat)
**FIXED**
* 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-01 Added Label Pools
* 2015-09-29 Moved Asm6502 out of Struse Samples.
* 2015-09-28 First commit

View File

@ -44,10 +44,17 @@
// Max number of nested scopes (within { and })
#define MAX_SCOPE_DEPTH 32
// Max number of nested conditional expressions
#define MAX_CONDITIONAL_DEPTH 64
// The maximum complexity of expressions to be evaluated
#define MAX_EVAL_VALUES 32
#define MAX_EVAL_OPER 64
// Max capacity of each label pool
#define MAX_POOL_RANGES 4
#define MAX_POOL_BYTES 128
// Internal status and error type
enum StatusCode {
STATUS_OK, // everything is fine
@ -66,6 +73,11 @@ enum StatusCode {
ERROR_UNEXPECTED_CHARACTER_IN_ADDRESSING_MODE,
ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT,
ERROR_MODIFYING_CONST_LABEL,
ERROR_OUT_OF_LABELS_IN_POOL,
ERROR_INTERNAL_LABEL_POOL_ERROR,
ERROR_POOL_RANGE_EXPRESSION_EVAL,
ERROR_LABEL_POOL_REDECLARATION,
ERROR_POOL_LABEL_ALREADY_DEFINED,
ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution
@ -75,10 +87,15 @@ enum StatusCode {
ERROR_BAD_MACRO_FORMAT,
ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY,
ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION,
ERROR_CONDITION_COULD_NOT_BE_RESOLVED,
ERROR_ENDIF_WITHOUT_CONDITION,
ERROR_ELSE_WITHOUT_IF,
STATUSCODE_COUNT
};
// The following strings are in the same order as StatusCode
const char *aStatusStrings[] = {
const char *aStatusStrings[STATUSCODE_COUNT] = {
"ok",
"not ready",
"Unexpected character in expression",
@ -95,13 +112,23 @@ const char *aStatusStrings[] = {
"Unexpected character in addressing mode",
"Unexpected label assignment format",
"Changing value of label that is constant",
"Out of labels in pool",
"Internal label pool release confusion",
"Label pool range evaluation failed",
"Label pool was redeclared within its scope",
"Pool label already defined",
"Errors after this point will stop execution",
"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",
"Conditional could not be resolved",
"#endif encountered outside conditional block",
"#else or #elif outside conditional block",
};
// Operators are either instructions or directives
@ -172,6 +199,8 @@ enum OP_INDICES {
OPI_JMP = 1,
};
#define RELATIVE_JMP_DELTA 0x20
// opcode names in groups (prefix by group size)
const char aInstr[] = {
"BRK,JSR,RTI,RTS\n"
@ -205,10 +234,13 @@ unsigned char CC00Mask[] = { 0x0a, 0x08, 0x08, 0x2a, 0xae, 0x0e, 0x0e };
unsigned char CC10ModeAdd[] = { 0xff, 4, 0, 12, 0xff, 20, 0xff, 28 };
unsigned char CC10Mask[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0xae, 0xaa, 0xaa };
// hardtexted strings
static const strref c_comment("//");
static const strref word_char_range("!0-9a-zA-Z_@$!");
static const strref word_char_range("!0-9a-zA-Z_@$!#");
static const strref label_char_range("!0-9a-zA-Z_@$!.");
static const strref keyword_equ("equ");
static const strref str_label("label");
static const strref str_const("const");
// pairArray is basically two vectors sharing a size without using constructors
template <class H, class V> class pairArray {
@ -324,12 +356,31 @@ typedef struct {
// Source context is current file (include file, etc.) or current macro.
typedef struct {
strref source_name; // source file name (error output)
strref source_file; // entire source file (req. for line #)
strref code_segment; // the segment of the file for this context
strref read_source; // current position/length in source file
strref source_name; // source file name (error output)
strref source_file; // entire source file (req. for line #)
strref code_segment; // the segment of the file for this context
strref read_source; // current position/length in source file
} SourceContext;
// All local labels are removed when a global label is defined but some when a scope ends
typedef struct {
strref label;
int scope_depth;
bool scope_reserve; // not released for global label, only scope
} LocalLabelRecord;
// Label pools allows C like stack frame label allocation
typedef struct {
strref pool_name;
short numRanges; // normally 1 range, support multiple for ease of use
short scopeDepth; // for scope closure cleanup
unsigned short ranges[MAX_POOL_RANGES*2]; // 2 shorts per range
unsigned int usedMap[(MAX_POOL_BYTES+15)>>4]; // 2 bits per byte to store byte count of label
StatusCode Reserve(int numBytes, unsigned int &addr);
StatusCode Release(unsigned int addr);
} LabelPool;
// Context stack is a stack of currently processing text
class ContextStack {
private:
std::vector<SourceContext> stack;
@ -365,6 +416,12 @@ enum AssemblerDirective {
AD_CONST,
AD_LABEL,
AD_INCSYM,
AD_LABPOOL,
AD_IF,
AD_IFDEF,
AD_ELSE,
AD_ELIF,
AD_ENDIF,
};
// The state of the assembly
@ -372,9 +429,10 @@ class Asm {
public:
pairArray<unsigned int, Label> labels;
pairArray<unsigned int, Macro> macros;
pairArray<unsigned int, LabelPool> labelPools;
std::vector<LateEval> lateEval;
std::vector<strref> localLabels; // remove these labels when a global pc label is added
std::vector<char*> loadedData; // free when
std::vector<LocalLabelRecord> localLabels; // remove these labels when a global pc label is added
std::vector<char*> loadedData; // free when assembly is completed
strovl symbols;
// context for macros / include files
@ -388,8 +446,11 @@ public:
unsigned int load_address;
int scope_address[MAX_SCOPE_DEPTH];
int scope_depth;
int conditional_depth;
char conditional_nesting[MAX_CONDITIONAL_DEPTH];
bool conditional_consumed[MAX_CONDITIONAL_DEPTH];
bool set_load_address;
bool symbol_export;
bool symbol_export, last_label_local;
// Convert source to binary
void Assemble(strref source, strref filename);
@ -408,14 +469,26 @@ public:
StatusCode EvalExpression(strref expression, int pc, int scope_pc,
int scope_end_pc, int &result);
// Conditional statement eval
StatusCode EvalStatement(strref line, bool &result);
// Access labels
Label* GetLabel(strref label);
Label* AddLabel(unsigned int hash);
StatusCode AssignLabel(strref label, strref line, bool make_constant = false);
StatusCode AddressLabel(strref label);
void LabelAdded(Label *pLabel);
void IncSym(strref line);
void LabelAdded(Label *pLabel, bool local=false);
void IncludeSymbols(strref line);
// Manage locals
void MarkLabelLocal(strref label, bool scope_label = false);
void FlushLocalLabels(int scope_exit = -1);
// Label pools
LabelPool* GetLabelPool(strref pool_name);
StatusCode AddLabelPool(strref name, strref args);
StatusCode AssignPoolLabel(LabelPool &pool, strref args);
void FlushLabelPools(int scope_exit);
// Late expression evaluation
void AddLateEval(int pc, int scope_pc, unsigned char *target,
@ -424,10 +497,6 @@ public:
strref expression, LateEvalType type);
StatusCode CheckLateEval(strref added_label=strref(), int scope_end = -1);
// Manage locals
void MarkLabelLocal(strref label);
void FlushLocalLabels();
// Assembler steps
StatusCode ApplyDirective(AssemblerDirective dir, strref line, strref source_file);
AddressingMode GetAddressMode(strref line, bool flipXY,
@ -436,9 +505,10 @@ public:
StatusCode BuildSegment(OP_ID *pInstr, int numInstructions);
// constructor
Asm() : address(0x1000), load_address(0x1000), scope_depth(0), set_load_address(false),
output(nullptr), curr(nullptr), output_capacity(0), symbol_export(false)
{ localLabels.reserve(256); }
Asm() {
Cleanup();
localLabels.reserve(256);
}
};
// Binary search over an array of unsigned integers, may contain multiple instances of same key
@ -513,6 +583,7 @@ void Asm::Cleanup() {
free(symbols.charstr());
symbols.set_overlay(nullptr,0);
}
labelPools.clear();
loadedData.clear();
labels.clear();
macros.clear();
@ -521,6 +592,17 @@ void Asm::Cleanup() {
output = nullptr;
curr = nullptr;
output_capacity = 0;
address = 0x1000;
load_address = 0x1000;
scope_depth = 0;
conditional_depth = 0;
conditional_nesting[0] = 0;
conditional_consumed[0] = false;
set_load_address = false;
output_capacity = false;
symbol_export = false;
last_label_local = false;
}
// Make sure there is room to assemble in
@ -576,28 +658,6 @@ StatusCode Asm::AddMacro(strref macro, strref source_name, strref source_file)
}
// mark a label as a local label
void Asm::MarkLabelLocal(strref label)
{
localLabels.push_back(label);
}
// find all local labels and remove them
void Asm::FlushLocalLabels()
{
std::vector<strref>::iterator i = localLabels.begin();
while (i!=localLabels.end()) {
unsigned int index = FindLabelIndex(i->fnv1a(), labels.getKeys(), labels.count());
while (index<labels.count()) {
if (i->same_str_case(labels.getValue(index).label_name)) {
labels.remove(index);
break;
}
}
i = localLabels.erase(i);
}
}
// if an expression could not be evaluated, add it along with
// the action to perform if it can be evaluated later.
void Asm::AddLateEval(int pc, int scope_pc, unsigned char *target, strref expression, strref source_file, LateEvalType type)
@ -686,7 +746,8 @@ StatusCode Asm::CheckLateEval(strref added_label, int scope_end)
if (num_new_labels<MAX_LABELS_EVAL_ALL)
new_labels[num_new_labels++] = label->label_name;
evaluated_label = true;
LabelAdded(label);
char f = i->label[0], l = i->label.get_last();
LabelAdded(label, f=='.' || f=='!' || f=='@' || l=='$');
break;
}
default:
@ -716,28 +777,31 @@ Label *Asm::GetLabel(strref label)
return nullptr;
}
static const strref str_label("label");
static const strref str_const("const");
// If exporting labels, append this label to the list
void Asm::LabelAdded(Label *pLabel)
void Asm::LabelAdded(Label *pLabel, bool local)
{
if (pLabel && pLabel->evaluated && symbol_export) {
int space = 1 + str_label.get_len() + 1 + pLabel->label_name.get_len() + 1 + 9 + 2;
if ((symbols.get_len()+space) > symbols.cap()) {
strl_t new_size = ((symbols.get_len()+space)+8*1024);
char *new_charstr = (char*)malloc(new_size);
if (symbols.charstr()) {
memcpy(new_charstr, symbols.charstr(), symbols.get_len());
free(symbols.charstr());
if (char *new_charstr = (char*)malloc(new_size)) {
if (symbols.charstr()) {
memcpy(new_charstr, symbols.charstr(), symbols.get_len());
free(symbols.charstr());
}
symbols.set_overlay(new_charstr, new_size, symbols.get_len());
}
symbols.set_overlay(new_charstr, new_size, symbols.get_len());
}
symbols.append('.');
if (local && !last_label_local)
symbols.append("{\n");
else if (!local && last_label_local)
symbols.append("}\n");
symbols.append(local ? " ." : ".");
symbols.append(pLabel->constant ? str_const : str_label);
symbols.append(' ');
symbols.append(pLabel->label_name);
symbols.sprintf_append("=$%04x\n", pLabel->value);
last_label_local = local;
}
}
@ -928,6 +992,224 @@ Label* Asm::AddLabel(unsigned int hash) {
return labels.getValues() + index;
}
// mark a label as a local label
void Asm::MarkLabelLocal(strref label, bool scope_reserve)
{
LocalLabelRecord rec;
rec.label = label;
rec.scope_depth = scope_depth;
rec.scope_reserve = scope_reserve;
localLabels.push_back(rec);
}
// find all local labels or up to given scope level and remove them
void Asm::FlushLocalLabels(int scope_exit)
{
// iterate from end of local label records and early out if the label scope is lower than the current.
std::vector<LocalLabelRecord>::iterator i = localLabels.end();
while (i!=localLabels.begin()) {
--i;
if (i->scope_depth < scope_depth)
break;
strref label = i->label;
if (!i->scope_reserve || i->scope_depth<=scope_exit) {
unsigned int index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count());
while (index<labels.count()) {
if (label.same_str_case(labels.getValue(index).label_name)) {
if (i->scope_reserve) {
if (LabelPool *pool = GetLabelPool(labels.getValue(index).expression)) {
pool->Release(labels.getValue(index).value);
break;
}
}
labels.remove(index);
break;
}
++index;
}
i = localLabels.erase(i);
}
}
}
// Get a label pool by name
LabelPool* Asm::GetLabelPool(strref pool_name)
{
unsigned int pool_hash = pool_name.fnv1a();
unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
while (ins < labelPools.count() && pool_hash == labelPools.getKey(ins)) {
if (pool_name.same_str(labelPools.getValue(ins).pool_name)) {
return &labelPools.getValue(ins);
}
ins++;
}
return nullptr;
}
// When going out of scope, label pools are deleted.
void Asm::FlushLabelPools(int scope_exit)
{
unsigned int i = 0;
while (i<labelPools.count()) {
if (labelPools.getValue(i).scopeDepth >= scope_exit)
labelPools.remove(i);
else
++i;
}
}
// Add a label pool
StatusCode Asm::AddLabelPool(strref name, strref args)
{
unsigned int pool_hash = name.fnv1a();
unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
unsigned int index = ins;
while (index < labelPools.count() && pool_hash == labelPools.getKey(index)) {
if (name.same_str(labelPools.getValue(index).pool_name))
return ERROR_LABEL_POOL_REDECLARATION;
index++;
}
// check that there is at least one valid address
int ranges = 0;
int num32 = 0;
unsigned short aRng[256];
while (strref arg = args.split_token_trim(',')) {
strref start = arg[0]=='(' ? arg.scoped_block_skip() : arg.split_token_trim('-');
int addr0 = 0, addr1 = 0;
if (STATUS_OK != EvalExpression(start, address, scope_address[scope_depth], -1, addr0))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
if (STATUS_OK != EvalExpression(arg, address, scope_address[scope_depth], -1, addr1))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
if (addr1<=addr0 || addr0<0)
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
aRng[ranges++] = addr0;
aRng[ranges++] = addr1;
num32 += (addr1-addr0+15)>>4;
if (ranges >(MAX_POOL_RANGES*2) ||
num32 > ((MAX_POOL_BYTES+15)>>4))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
}
if (!ranges)
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
LabelPool pool;
pool.pool_name = name;
pool.numRanges = ranges>>1;
pool.scopeDepth = scope_depth;
memset(pool.usedMap, 0, sizeof(unsigned int) * num32);
for (int r = 0; r<ranges; r++)
pool.ranges[r] = aRng[r];
labelPools.insert(ins, pool_hash);
LabelPool &poolValue = labelPools.getValue(ins);
poolValue = pool;
return STATUS_OK;
}
StatusCode Asm::AssignPoolLabel(LabelPool &pool, strref label)
{
strref type = label;
label = type.split_token('.');
int bytes = 1;
if (strref::tolower(type[0])=='w')
bytes = 2;
if (GetLabel(label))
return ERROR_POOL_LABEL_ALREADY_DEFINED;
unsigned int addr;
StatusCode error = pool.Reserve(bytes, addr);
if (error != STATUS_OK)
return error;
Label *pLabel = AddLabel(label.fnv1a());
pLabel->label_name = label;
pLabel->expression = pool.pool_name;
pLabel->evaluated = true;
pLabel->value = addr;
pLabel->zero_page = addr<0x100;
pLabel->pc_relative = true;
pLabel->constant = true;
MarkLabelLocal(label, true);
return error;
}
// Request a label from a pool
StatusCode LabelPool::Reserve(int numBytes, unsigned int &ret_addr)
{
unsigned int *map = usedMap;
unsigned short *pRanges = ranges;
for (int r = 0; r<numRanges; r++) {
int sequence = 0;
unsigned int a0 = *pRanges++, a1 = *pRanges++;
unsigned int addr = a1-1, *range_map = map;
while (addr>=a0 && sequence<numBytes) {
unsigned int chk = *map++, m = 3;
while (m && addr >= a0) {
if ((m & chk)==0) {
sequence++;
if (sequence == numBytes)
break;
} else
sequence = 0;
--addr;
m <<= 2;
}
}
if (sequence == numBytes) {
unsigned int index = (a1-addr-numBytes);
unsigned int *addr_map = range_map + (index>>4);
unsigned int m = numBytes << (index << 1);
for (int b = 0; b<numBytes; b++) {
*addr_map |= m;
unsigned int _m = m << 2;
if (!_m) { m <<= 30; addr_map++; } else { m = _m; }
}
ret_addr = addr;
return STATUS_OK;
}
}
return ERROR_OUT_OF_LABELS_IN_POOL;
}
// Release a label from a pool (at scope closure)
StatusCode LabelPool::Release(unsigned int addr) {
unsigned int *map = usedMap;
unsigned short *pRanges = ranges;
for (int r = 0; r<numRanges; r++) {
unsigned short a0 = *pRanges++, a1 = *pRanges++;
if (addr>=a0 && addr<a1) {
unsigned int index = (a1-addr-1);
map += index>>4;
index &= 0xf;
unsigned int u = *map, m = 3 << (index << 1);
unsigned int b = u & m, bytes = b >> (index << 1);
if (bytes) {
for (unsigned int f = 0; f<bytes; f++) {
u &= ~m;
unsigned int _m = m>>2;
if (!_m) { m <<= 30; *map-- = u; } else { m = _m; }
}
*map = u;
return STATUS_OK;
} else
return ERROR_INTERNAL_LABEL_POOL_ERROR;
} else
map += (a1-a0+15)>>4;
}
return STATUS_OK;
}
// unique key binary search
int LookupOpCodeIndex(unsigned int hash, OP_ID *lookup, int count)
{
@ -967,6 +1249,13 @@ DirectiveName aDirectiveNames[] {
{ "CONST", AD_CONST },
{ "LABEL", AD_LABEL },
{ "INCSYM", AD_INCSYM },
{ "LABPOOL", AD_LABPOOL },
{ "POOL", AD_LABPOOL },
{ "#IF", AD_IF },
{ "#IFDEF", AD_IFDEF },
{ "#ELSE", AD_ELSE },
{ "#ELIF", AD_ELIF },
{ "#ENDIF", AD_ENDIF },
};
static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]);
@ -1083,10 +1372,47 @@ AddressingMode Asm::GetAddressMode(strref line, bool flipXY, StatusCode &error,
return addrMode;
}
// Conditional statement eval (true/false)
StatusCode Asm::EvalStatement(strref line, bool &result)
{
int equ = line.find('=');
if (equ >=0) {
// (EXP) == (EXP)
strref left = line.get_clipped(equ);
bool equal = left.get_last()!='!';
left.trim_whitespace();
strref right = line + equ + 1;
if (right.get_first()=='=')
++right;
right.trim_whitespace();
int value_left, value_right;
if (STATUS_OK != EvalExpression(left, address, scope_address[scope_depth], -1, value_left))
return ERROR_CONDITION_COULD_NOT_BE_RESOLVED;
if (STATUS_OK != EvalExpression(right, address, scope_address[scope_depth], -1, value_right))
return ERROR_CONDITION_COULD_NOT_BE_RESOLVED;
result = (value_left==value_right && equal) || (value_left!=value_right && !equal);
} else {
bool invert = line.get_first()=='!';
if (invert)
++line;
int value;
if (STATUS_OK != EvalExpression(line, address, scope_address[scope_depth], -1, value))
return ERROR_CONDITION_COULD_NOT_BE_RESOLVED;
result = (value!=0 && !invert) || (value==0 && invert);
}
return STATUS_OK;
}
// Action based on assembler directive
StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref source_file)
{
StatusCode error = STATUS_OK;
if (conditional_nesting[conditional_depth]) {
if (dir!=AD_IF && dir!=AD_IFDEF && dir!=AD_ELSE && dir!=AD_ELIF && dir!=AD_ELSE && dir!=AD_ENDIF)
return STATUS_OK;
}
switch (dir) {
case AD_ORG: { // org / pc: current address of code
int addr;
@ -1122,6 +1448,8 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
}
case AD_ALIGN: // align: align address to multiple of value, fill space with 0
if (line) {
if (line[0]=='=' || keyword_equ.is_prefix_word(line))
line.next_word_ws();
int value;
int status = EvalExpression(line, address, scope_address[scope_depth], -1, value);
if (status == STATUS_NOT_READY)
@ -1137,14 +1465,23 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
break;
case AD_EVAL: { // eval: display the result of an expression in stdout
int value = 0;
strref description = line.split_token_trim(':');
strref description = line.find(':')>=0 ? line.split_token_trim(':') : strref();
line.trim_whitespace();
if (line && EvalExpression(line, address, scope_address[scope_depth], -1, value) == STATUS_OK)
printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(description)+1, STRREF_ARG(description), STRREF_ARG(line), value);
else
if (line && EvalExpression(line, address, scope_address[scope_depth], -1, value) == STATUS_OK) {
if (description) {
printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(description)+1, STRREF_ARG(description), STRREF_ARG(line), value);
} else {
printf("EVAL(%d): \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(line)+1, STRREF_ARG(line), value);
}
} else if (description) {
printf("EVAL(%d): \"" STRREF_FMT ": " STRREF_FMT"\"\n",
contextStack.curr().source_file.count_lines(description)+1, STRREF_ARG(description), STRREF_ARG(line));
contextStack.curr().source_file.count_lines(description)+1, STRREF_ARG(description), STRREF_ARG(line));
} else {
printf("EVAL(%d): \"" STRREF_FMT "\"\n",
contextStack.curr().source_file.count_lines(line)+1, STRREF_ARG(line));
}
break;
}
case AD_BYTES: // bytes: add bytes by comma separated values/expressions
@ -1171,7 +1508,7 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
CheckOutputCapacity(2);
*curr++ = (char)value;
*curr++ = (char)(value>>8);
address+=2;
address += 2;
}
break;
case AD_TEXT: { // text: add text within quotes
@ -1208,7 +1545,7 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
break;
}
case AD_MACRO: { // macro: create an assembler macro
strref from_here = contextStack.curr().code_segment +
strref from_here = contextStack.curr().code_segment +
strl_t(line.get()-contextStack.curr().code_segment.get());
int block_start = from_here.find('{');
if (block_start > 0) {
@ -1255,10 +1592,92 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
break;
}
case AD_INCSYM: {
IncSym(line);
IncludeSymbols(line);
break;
}
case AD_LABPOOL: {
strref label = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0);
AddLabelPool(label, line);
break;
}
case AD_IF: {
if (conditional_nesting[conditional_depth])
conditional_nesting[conditional_depth]++;
else {
// #if within #if?
if (conditional_consumed[conditional_depth]) {
conditional_depth++;
conditional_consumed[conditional_depth] = false;
conditional_nesting[conditional_depth] = 0;
}
bool conditional_result;
error = EvalStatement(line, conditional_result);
if (conditional_result)
conditional_consumed[conditional_depth] = true;
else
conditional_nesting[conditional_depth] = 1;
}
break;
}
case AD_IFDEF:
if (conditional_nesting[conditional_depth])
conditional_nesting[conditional_depth]++;
else {
// #if within #if?
if (conditional_consumed[conditional_depth]) {
conditional_depth++;
conditional_consumed[conditional_depth] = false;
conditional_nesting[conditional_depth] = 0;
}
if (GetLabel(line.get_trimmed_ws()) == nullptr)
conditional_nesting[conditional_depth] = 1;
else
conditional_consumed[conditional_depth] = true;
}
break;
case AD_ELSE:
if (conditional_nesting[conditional_depth]==0) {
if (conditional_consumed[conditional_depth])
conditional_nesting[conditional_depth]++;
else
error = ERROR_ELSE_WITHOUT_IF;
} else if (conditional_nesting[conditional_depth]==1 &&
!conditional_consumed[conditional_depth]) {
conditional_nesting[conditional_depth] = 0;
conditional_consumed[conditional_depth] = true;
}
break;
case AD_ELIF:
if (conditional_nesting[conditional_depth]==0) {
if (conditional_consumed[conditional_depth])
conditional_nesting[conditional_depth]++;
else
error = ERROR_ELSE_WITHOUT_IF;
}
if (conditional_nesting[conditional_depth]==1 &&
!conditional_consumed[conditional_depth]) {
bool conditional_result;
error = EvalStatement(line, conditional_result);
if (conditional_result) {
conditional_nesting[conditional_depth] = 0;
conditional_consumed[conditional_depth] = true;
}
}
break;
case AD_ENDIF:
if (conditional_nesting[conditional_depth]) {
conditional_nesting[conditional_depth]--;
if (!conditional_nesting[conditional_depth]) {
conditional_consumed[conditional_depth] = false;
if (conditional_depth)
conditional_depth--;
}
} else if (conditional_consumed[conditional_depth]) {
conditional_consumed[conditional_depth] = false;
if (conditional_depth)
conditional_depth--;
} else
error = ERROR_ENDIF_WITHOUT_CONDITION;
}
return error;
}
@ -1314,7 +1733,7 @@ StatusCode Asm::AddOpcode(strref line, int group, int index, strref source_file)
break;
case OPG_SUBROUT:
if (index==1) { // jsr
if (index==OPI_JSR) { // jsr
if (addrMode != AM_ABSOLUTE)
error = ERROR_INVALID_ADDRESSING_MODE_FOR_BRANCH;
else
@ -1329,8 +1748,8 @@ StatusCode Asm::AddOpcode(strref line, int group, int index, strref source_file)
case OPG_CC00:
// jump relative exception
if (addrMode==AM_RELATIVE && index==OPI_JMP) {
base_opcode += 0x20;
addrMode = AM_ABSOLUTE;
base_opcode += RELATIVE_JMP_DELTA;
addrMode = AM_ABSOLUTE; // the relative address is in an absolute location ;)
}
if (addrMode>7 || (CC00Mask[index]&(1<<addrMode))==0)
error = ERROR_BAD_ADDRESSING_MODE;
@ -1348,7 +1767,6 @@ StatusCode Asm::AddOpcode(strref line, int group, int index, strref source_file)
}
}
break;
case OPG_CC01:
if (addrMode>7 || (addrMode==AM_IMMEDIATE && index==OPI_STA))
error = ERROR_BAD_ADDRESSING_MODE;
@ -1394,7 +1812,6 @@ StatusCode Asm::AddOpcode(strref line, int group, int index, strref source_file)
break;
}
}
// Add the instruction and argument to the code
if (error == STATUS_OK) {
CheckOutputCapacity(4);
@ -1474,29 +1891,33 @@ StatusCode Asm::BuildMacro(Macro &m, strref arg_list)
return STATUS_OK;
}
void Asm::IncSym(strref line)
// include symbols listed from a .sym file or all if no listing
void Asm::IncludeSymbols(strref line)
{
// include symbols listed or all if no listing
strref symlist = line.before('"').get_trimmed_ws();
line = line.between('"', '"');
size_t size;
if (char *buffer = LoadText(line, size)) {
strref symfile(buffer, strl_t(size));
while (strref symdef = symfile.line()) {
strref symtype = symdef.split_token(' ');
strref label = symdef.split_token_trim('=');
// first word is either .label or .const
bool constant = symtype.same_str(".const");
if (symlist) {
strref symchk = symlist;
while (strref symwant = symchk.split_token_trim(',')) {
if (symwant.same_str_case(label)) {
AssignLabel(label, symdef, constant);
break;
while (symfile) {
symfile.skip_whitespace();
if (symfile[0]=='{') // don't include local labels
symfile.scoped_block_skip();
if (strref symdef = symfile.line()) {
strref symtype = symdef.split_token(' ');
strref label = symdef.split_token_trim('=');
bool constant = symtype.same_str(".const"); // first word is either .label or .const
if (symlist) {
strref symchk = symlist;
while (strref symwant = symchk.split_token_trim(',')) {
if (symwant.same_str_case(label)) {
AssignLabel(label, symdef, constant);
break;
}
}
}
} else
AssignLabel(label, symdef, constant);
} else
AssignLabel(label, symdef, constant);
}
}
loadedData.push_back(buffer);
}
@ -1526,15 +1947,19 @@ StatusCode Asm::AssignLabel(strref label, strref line, bool make_constant)
pLabel->pc_relative = false;
pLabel->constant = make_constant;
bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label.get_last()=='$';
if (!pLabel->evaluated)
AddLateEval(label, address, scope_address[scope_depth], line, LET_LABEL);
else {
LabelAdded(pLabel);
if (local)
MarkLabelLocal(label);
LabelAdded(pLabel, local);
return CheckLateEval(label);
}
return STATUS_OK;
}
// Adding a fixed address label
StatusCode Asm::AddressLabel(strref label)
{
Label *pLabel = GetLabel(label);
@ -1553,8 +1978,9 @@ StatusCode Asm::AddressLabel(strref label)
pLabel->pc_relative = true;
pLabel->constant = constLabel;
pLabel->zero_page = false;
LabelAdded(pLabel);
if (label[0]=='.' || label[0]=='@' || label[0]=='!' || label.get_last()=='$')
bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label.get_last()=='$';
LabelAdded(pLabel, local);
if (local)
MarkLabelLocal(label);
else
FlushLocalLabels();
@ -1581,26 +2007,30 @@ StatusCode Asm::BuildSegment(OP_ID *pInstr, int numInstructions)
++operation;
}
if (!operation) {
// scope open / close
switch (line[0]) {
case '{':
if (scope_depth>=(MAX_SCOPE_DEPTH-1))
error = ERROR_TOO_DEEP_SCOPE;
else {
scope_address[++scope_depth] = address;
if (!conditional_nesting[conditional_depth]) {
// scope open / close
switch (line[0]) {
case '{':
if (scope_depth>=(MAX_SCOPE_DEPTH-1))
error = ERROR_TOO_DEEP_SCOPE;
else {
scope_address[++scope_depth] = address;
++line;
line.skip_whitespace();
}
break;
case '}':
// check for late eval of anything with an end scope
CheckLateEval(strref(), address);
FlushLocalLabels(scope_depth);
FlushLabelPools(scope_depth);
--scope_depth;
if (scope_depth<0)
error = ERROR_UNBALANCED_SCOPE_CLOSURE;
++line;
line.skip_whitespace();
}
break;
case '}':
// check for late eval of anything with an end scope
CheckLateEval(strref(), address);
--scope_depth;
if (scope_depth<0)
error = ERROR_UNBALANCED_SCOPE_CLOSURE;
++line;
line.skip_whitespace();
break;
break;
}
}
} else {
// ignore leading period for instructions and directives - not for labels
@ -1609,14 +2039,15 @@ StatusCode Asm::BuildSegment(OP_ID *pInstr, int numInstructions)
if (op_idx >= 0 && line[0]!=':') {
if (pInstr[op_idx].type==OT_DIRECTIVE) {
error = ApplyDirective((AssemblerDirective)pInstr[op_idx].index, line, contextStack.curr().source_file);
line.clear();
} else if (pInstr[op_idx].type==OT_MNEMONIC) {
} else if (!conditional_nesting[conditional_depth] && pInstr[op_idx].type==OT_MNEMONIC) {
OP_ID &id = pInstr[op_idx];
int group = id.group;
int index = id.index;
error = AddOpcode(line, group, index, contextStack.curr().source_file);
line.clear();
}
line.clear();
} else if (conditional_nesting[conditional_depth]) {
line.clear(); // do nothing if conditional nesting so clear the current line
} else if (line.get_first()=='=') {
++line;
error = AssignLabel(label, line);
@ -1624,20 +2055,33 @@ StatusCode Asm::BuildSegment(OP_ID *pInstr, int numInstructions)
} else {
unsigned int nameHash = label.fnv1a();
unsigned int macro = FindLabelIndex(nameHash, macros.getKeys(), macros.count());
bool gotMacro = false;
bool gotConstruct = false;
while (macro < macros.count() && nameHash==macros.getKey(macro)) {
if (macros.getValue(macro).name.same_str_case(label)) {
error = BuildMacro(macros.getValue(macro), line);
gotMacro = true;
gotConstruct = true;
line.clear(); // don't process codes from here
break;
}
macro++;
}
if (!gotMacro) {
error = AddressLabel(label);
if (line[0]==':')
++line;
if (!gotConstruct) {
unsigned int labPool = FindLabelIndex(nameHash, labelPools.getKeys(), labelPools.count());
gotConstruct = false;
while (labPool < labelPools.count() && nameHash==labelPools.getKey(labPool)) {
if (labelPools.getValue(labPool).pool_name.same_str_case(label)) {
error = AssignPoolLabel(labelPools.getValue(labPool), line);
gotConstruct = true;
line.clear(); // don't process codes from here
break;
}
labPool++;
}
if (!gotConstruct) {
error = AddressLabel(label);
if (line[0]==':')
++line;
}
// there may be codes after the label
}
}
@ -1685,6 +2129,11 @@ void Asm::Assemble(strref source, strref filename)
errorText.append(aStatusStrings[error]);
fwrite(errorText.get(), errorText.get_len(), 1, stderr);
}
// close last local label of symbol file
if (symbol_export && last_label_local)
symbols.append("}\n");
for (std::vector<LateEval>::iterator i = lateEval.begin(); i!=lateEval.end(); ++i) {
strown<512> errorText;
int line = i->source_file.count_lines(i->expression);