From e2b2a19808c2ff20b5d7ced824238aa64410b781 Mon Sep 17 00:00:00 2001 From: Charles Reilly Date: Sat, 26 May 2018 23:34:02 +0100 Subject: [PATCH 001/144] Seg fault if last line of basic file unterminated --- src/BASIC.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/BASIC.cpp b/src/BASIC.cpp index ff95f62..823680c 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -694,7 +694,8 @@ bool EncodeLine() } } - EatCharacters(1); //either eat a '\n' or have no effect at all + if (!EndOfFile && Token == '\n' && !ErrorNum) + EatCharacters(1); // Eat a '\n' return true; } From beef190f0ef4af8e66a2e06ea11725d615ce77cd Mon Sep 17 00:00:00 2001 From: Charles Reilly Date: Thu, 31 May 2018 22:37:50 +0100 Subject: [PATCH 002/144] Document COPYBLOCK Resolves #31 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e1ee67d..7da47b5 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,11 @@ Moves the address pointer to the specified address. An error is generated if th Used to align the address pointer to the next boundary, e.g. use `ALIGN &100` to move to the next page (useful perhaps for positioning a table at a page boundary so that index accesses don't incur a "page crossed" penalty. +`COPYBLOCK ,,` + +Copies a block of assembled data from one location to another. This is useful to copy code assembled at one location into a program's data area for relocation at run-time. + + `INCLUDE "filename"` Includes the specified source file in the code at this point. From 3ee1e6a865a1d8af664016cee5c1e21f1601f484 Mon Sep 17 00:00:00 2001 From: Charles Reilly Date: Thu, 31 May 2018 23:04:04 +0100 Subject: [PATCH 003/144] Buffer overflow if BASIC file ends with 2 blank lines Resolves #30 --- src/BASIC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BASIC.cpp b/src/BASIC.cpp index 823680c..0024ee5 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -754,7 +754,7 @@ bool ImportBASIC(const char *Filename, Uint8 *Mem, int* Size) { /* get line number */ /* skip white space and empty lines */ - while(Token == ' ' || Token == '\t' || Token == '\r' || Token == '\n') + while(!EndOfFile && (Token == ' ' || Token == '\t' || Token == '\r' || Token == '\n')) EatCharacters(1); /* end of file? */ From 61ffb43cd7afe6078f91fd5386a2ef6072250a75 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Thu, 31 May 2018 23:21:19 +0100 Subject: [PATCH 004/144] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7da47b5..5117c1f 100644 --- a/README.md +++ b/README.md @@ -711,7 +711,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Allow label scope-jumping using '.*label' and '.^label' Allow high bits to be set on load/execution addresses Show branch displacement when "Branch out of range" occurs - Fixed bugs in duplicate filename direction on disc image + Fixed bugs in duplicate filename detection on disc image Fixed spurious "Branch out of range" error in rare case 19/01/2012 1.08 Fixed makefile for GCC (MinGW) builds. Added COPYBLOCK command to manage blocks of memory. From 261721f3b2f4693d0fa0662973de5aa307ed1c5a Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Thu, 31 May 2018 23:22:17 +0100 Subject: [PATCH 005/144] Change version from v1.09 to v1.10-pre I am working on the assumption this will minimise confusion; binaries built from this branch will at least be distinguishable from v1.09 and the final v1.10. --- README.md | 3 ++- src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5117c1f..6b1e652 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # BeebAsm -**Version V1.09** +**Version V1.10-pre** A portable 6502 assembler with BBC Micro style syntax @@ -698,6 +698,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` +??/??/???? 1.10 ????? 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/src/main.cpp b/src/main.cpp index 2320893..d102fc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,7 +42,7 @@ using namespace std; -#define VERSION "1.09" +#define VERSION "1.10-pre" /*************************************************************************************************/ From 229ee57ab9efccc34d381413a40ffaa64cc7e35f Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Thu, 16 Apr 2020 23:12:37 +0100 Subject: [PATCH 006/144] Add test cases for issue #38 Current master shows an incorrect line number when assembling errorlinenumber1.6502. A naive fix by removing the -1 in the assignment to m_lineNumber in the 'reloop' case of SourceCode::UpdateFor() fixes that, but causes errorlinenumber2.6502 to report an incorrect line number. --- examples/errorlinenumber1.6502 | 17 +++++++++++++++++ examples/errorlinenumber2.6502 | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 examples/errorlinenumber1.6502 create mode 100644 examples/errorlinenumber2.6502 diff --git a/examples/errorlinenumber1.6502 b/examples/errorlinenumber1.6502 new file mode 100644 index 0000000..f7d1382 --- /dev/null +++ b/examples/errorlinenumber1.6502 @@ -0,0 +1,17 @@ +org &2000 +.start + +macro foo + for i, 0, 2 + nop + next + + lda #10 + ldx (&70),y +endmacro + +foo + +.end + +save "foo", start, end diff --git a/examples/errorlinenumber2.6502 b/examples/errorlinenumber2.6502 new file mode 100644 index 0000000..b585b14 --- /dev/null +++ b/examples/errorlinenumber2.6502 @@ -0,0 +1,15 @@ +org &2000 +.start + +for i, 0, 2 + nop +next + +lda #10 +ldx (&70),y + +foo + +.end + +save "foo", start, end From 75ffa1c9f066a5235050d1654d76d26d1a0da63f Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Thu, 16 Apr 2020 23:57:25 +0100 Subject: [PATCH 007/144] Don't completely ignore blank lines in macros This caused line numbers to be reported incorrectly when an error occured during macro expansion. --- src/lineparser.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 398cd63..cf38147 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -73,8 +73,10 @@ LineParser::~LineParser() /*************************************************************************************************/ void LineParser::Process() { + bool bProcessedSomething = false; while ( AdvanceAndCheckEndOfLine() ) // keep going until we reach the end of the line { + bProcessedSomething = true; // cout << m_line << endl << string( m_column, ' ' ) << "^" << endl; int oldColumn = m_column; @@ -282,6 +284,18 @@ void LineParser::Process() throw AsmException_SyntaxError_UnrecognisedToken( m_line, oldColumn ); } + + // If we didn't process anything, i.e. this is a blank line, we must still call SkipStatement() + // if we're defining a macro, otherwise blank lines inside macro definitions cause incorrect + // line numbers to be reported for errors when expanding a macro definition. + if ( !bProcessedSomething ) + { + if ( !m_sourceCode->IsIfConditionTrue() ) + { + m_column = 0; + SkipStatement(); + } + } } From 5077c101c4b9a75ed5a8bd89c235398131f507dc Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Fri, 17 Apr 2020 00:06:04 +0100 Subject: [PATCH 008/144] Add description of fix for issue #38 to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4be6a5a..5886f96 100644 --- a/README.md +++ b/README.md @@ -693,6 +693,8 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` +??/??/???? 1.10? Fixed incorrect line number for errors inside macros with + blank lines inside them. 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From 7d73c216f1c81b90f0908e709940ae452ed56d69 Mon Sep 17 00:00:00 2001 From: 6502opcode <44790317+6502opcode@users.noreply.github.com> Date: Sun, 26 Apr 2020 00:11:51 +0100 Subject: [PATCH 009/144] streampos in std namespace, not ifstream. --- src/commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.cpp b/src/commands.cpp index df961ef..7b393fc 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1754,7 +1754,7 @@ void LineParser::HandlePutFileCommon( bool bText ) { // swallow other half of CRLF/LFCR, if present int other_half = ( c == '\n' ) ? '\r' : '\n'; - ifstream::streampos p = inputFile.tellg(); + std::streampos p = inputFile.tellg(); if ( inputFile.get() != other_half ) { inputFile.seekg( p ); From 322619df04301e3be3a304f304c8b0227b88c396 Mon Sep 17 00:00:00 2001 From: Charles Reilly Date: Sat, 12 Sep 2020 16:33:47 +0100 Subject: [PATCH 010/144] Report error for badly formed decimal number In a statement like "JMP .label" the ".label" would be parsed as a floating point number, silently fail and return 0. --- src/expression.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/expression.cpp b/src/expression.cpp index 7659539..bc5bd52 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -129,6 +129,11 @@ double LineParser::GetValue() istringstream str( m_line ); str.seekg( m_column ); str >> value; + if (str.fail()) + { + // A decimal point with no number will cause this + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + } m_column = static_cast< size_t >( str.tellg() ); } else if ( m_column < m_line.length() && ( m_line[ m_column ] == '&' || m_line[ m_column ] == '$' ) ) From 6cde8989adcb03ce655cc420c7431ac665b21739 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 16:20:54 +0100 Subject: [PATCH 011/144] Fix tokenisation of pseudo-vars on rhs in PUTBASIC diff --git a/examples/basicrhstoken.6502 b/examples/basicrhstoken.6502 new file mode 100644 index 0000000..e46a8c2 --- /dev/null +++ b/examples/basicrhstoken.6502 @@ -0,0 +1 @@ +putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/examples/basicrhstoken.bas b/examples/basicrhstoken.bas new file mode 100644 index 0000000..f2d9a47 --- /dev/null +++ b/examples/basicrhstoken.bas @@ -0,0 +1,13 @@ +p=PAGE +PRINT p +?&900=PAGE MOD 256 +PRINT FNpage +PRINT ?&900 +?&901=TIME MOD 256 +PRINT ?&901 +PRINT FNtime +END +DEF FNpage +=PAGE +DEF FNtime +=TIME diff --git a/src/BASIC.cpp b/src/BASIC.cpp index ff95f62..eaac8a7 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -587,6 +587,8 @@ bool EncodeLine() if(Token == ':') // a colon always switches the tokeniser back to "start of statement" mode StartOfStatement = true; + else if(Token == '=') // an equals sign always switches the tokeniser back to "middle of statement" mode + StartOfStatement = false; // grab entire variables rather than allowing bits to be tokenised if @@ -676,9 +678,7 @@ bool EncodeLine() } } - if( - (Flags & 0x40) && StartOfStatement - ) + if((Flags & 0x40) && StartOfStatement) { /* pseudo-variable flag */ Memory[Addr-1] += 0x40; //adjust just-written token --- examples/basicrhstoken.6502 | 1 + examples/basicrhstoken.bas | 13 +++++++++++++ src/BASIC.cpp | 6 +++--- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 examples/basicrhstoken.6502 create mode 100644 examples/basicrhstoken.bas diff --git a/examples/basicrhstoken.6502 b/examples/basicrhstoken.6502 new file mode 100644 index 0000000..e46a8c2 --- /dev/null +++ b/examples/basicrhstoken.6502 @@ -0,0 +1 @@ +putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/examples/basicrhstoken.bas b/examples/basicrhstoken.bas new file mode 100644 index 0000000..f2d9a47 --- /dev/null +++ b/examples/basicrhstoken.bas @@ -0,0 +1,13 @@ +p=PAGE +PRINT p +?&900=PAGE MOD 256 +PRINT FNpage +PRINT ?&900 +?&901=TIME MOD 256 +PRINT ?&901 +PRINT FNtime +END +DEF FNpage +=PAGE +DEF FNtime +=TIME diff --git a/src/BASIC.cpp b/src/BASIC.cpp index ff95f62..eaac8a7 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -587,6 +587,8 @@ bool EncodeLine() if(Token == ':') // a colon always switches the tokeniser back to "start of statement" mode StartOfStatement = true; + else if(Token == '=') // an equals sign always switches the tokeniser back to "middle of statement" mode + StartOfStatement = false; // grab entire variables rather than allowing bits to be tokenised if @@ -676,9 +678,7 @@ bool EncodeLine() } } - if( - (Flags & 0x40) && StartOfStatement - ) + if((Flags & 0x40) && StartOfStatement) { /* pseudo-variable flag */ Memory[Addr-1] += 0x40; //adjust just-written token From 573da4e681fd16df756684e5e1ac32e184b856e2 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 16:33:52 +0100 Subject: [PATCH 012/144] Add .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..57b3dbb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# https://editorconfig.org/ + +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.{c,h,cpp}] +indent_style = tab --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..57b3dbb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# https://editorconfig.org/ + +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.{c,h,cpp}] +indent_style = tab From a6bfbbc9069ecd428be14f7c4614d193517e208e Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 18:42:26 +0100 Subject: [PATCH 013/144] Document "$" and "%" literal prefixes --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b65d3c2..2be5924 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Instructions can be written one-per-line, or many on one line, separated by colo Comments are introduced by a semicolon or backslash. Unlike the BBC Micro assembler, these continue to the end of the line, and are not terminated by a colon (because this BBC Micro feature is horrible!). -Numeric literals are in decimal by default, and can be integers or reals. Hex literals are prefixed with `"&"`. A character in single quotes (e.g. `'A'`) returns its ASCII code. +Numeric literals are in decimal by default, and can be integers or reals. Hex literals are prefixed with `"&"` or `"$"`, binary literals are prefixed with `"%"`. A character in single quotes (e.g. `'A'`) returns its ASCII code. BeebAsm can accept complex expressions, using a wide variety of operators and functions. Here's a summary: @@ -698,7 +698,9 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` -??/??/???? 1.10 ????? +??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to + cardboardguru76) for pointing this out + TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From fb04ba3403f170543a7e79e54e33ca49327d0857 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 18:48:49 +0100 Subject: [PATCH 014/144] Document fix for "JMP .label" problem --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2be5924..13c17f6 100644 --- a/README.md +++ b/README.md @@ -699,7 +699,9 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` ??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to - cardboardguru76) for pointing this out + cardboardguru76 for pointing this out) + Fixed silently treating label references starting with "." + as 0 (thanks to mungre for this fix) TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) From 612a326fdf7fad5c2f192b19abd1afe57f49573b Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 18:54:11 +0100 Subject: [PATCH 015/144] Allow -h, -help or --help to generate help diff --git a/src/main.cpp b/src/main.cpp index d102fc0..d96d3fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,7 +138,9 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_SYMBOL; } - else if ( strcmp( argv[i], "--help" ) == 0 ) + else if ( ( strcmp( argv[i], "--help" ) == 0 ) || + ( strcmp( argv[i], "-help" ) == 0 ) || + ( strcmp( argv[i], "-h" ) == 0 ) ) { cout << "beebasm " VERSION << endl << endl; cout << "Possible options:" << endl; --- src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d102fc0..d96d3fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,7 +138,9 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_SYMBOL; } - else if ( strcmp( argv[i], "--help" ) == 0 ) + else if ( ( strcmp( argv[i], "--help" ) == 0 ) || + ( strcmp( argv[i], "-help" ) == 0 ) || + ( strcmp( argv[i], "-h" ) == 0 ) ) { cout << "beebasm " VERSION << endl << endl; cout << "Possible options:" << endl; From 4d557b27139db80a3af0d005a180dc3e5e19e660 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 19:05:53 +0100 Subject: [PATCH 016/144] Add more changes to README.md diff --git a/README.md b/README.md index 13c17f6..be6e885 100644 --- a/README.md +++ b/README.md @@ -702,6 +702,8 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre cardboardguru76 for pointing this out) Fixed silently treating label references starting with "." as 0 (thanks to mungre for this fix) + Allowed "-h" and "-help" options to see help + Fixed tokenisation of BASIC pseudo-variables in some cases TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 13c17f6..be6e885 100644 --- a/README.md +++ b/README.md @@ -702,6 +702,8 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre cardboardguru76 for pointing this out) Fixed silently treating label references starting with "." as 0 (thanks to mungre for this fix) + Allowed "-h" and "-help" options to see help + Fixed tokenisation of BASIC pseudo-variables in some cases TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) From 0df9fe56937ff49a16e0421911084d6c828b0a83 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 19:13:09 +0100 Subject: [PATCH 017/144] Tweak README.md changelog diff --git a/README.md b/README.md index 5602574..036369a 100644 --- a/README.md +++ b/README.md @@ -699,13 +699,13 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` ??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to - cardboardguru76 for pointing this out) + cardboardguru76 for pointing this out). Fixed silently treating label references starting with "." - as 0 (thanks to mungre for this fix) - Allowed "-h" and "-help" options to see help - Fixed tokenisation of BASIC pseudo-variables in some cases - Fixed incorrect line number for errors inside macros with - blank lines inside them. + as 0 (thanks to mungre for this fix). + Allowed "-h" and "-help" options to see help. + Fixed tokenisation of BASIC pseudo-variables in some cases. + Fixed incorrect line number for errors inside macros with + blank lines inside them. TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5602574..036369a 100644 --- a/README.md +++ b/README.md @@ -699,13 +699,13 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` ??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to - cardboardguru76 for pointing this out) + cardboardguru76 for pointing this out). Fixed silently treating label references starting with "." - as 0 (thanks to mungre for this fix) - Allowed "-h" and "-help" options to see help - Fixed tokenisation of BASIC pseudo-variables in some cases - Fixed incorrect line number for errors inside macros with - blank lines inside them. + as 0 (thanks to mungre for this fix). + Allowed "-h" and "-help" options to see help. + Fixed tokenisation of BASIC pseudo-variables in some cases. + Fixed incorrect line number for errors inside macros with + blank lines inside them. TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) From dfeb1dd64ad18b3571cca78004b4e016dc324ce2 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 19:25:16 +0100 Subject: [PATCH 018/144] Fix incorrect line numbers from PUTBASIC This could be seen with examples/invalidbasic* diff --git a/src/BASIC.cpp b/src/BASIC.cpp index dec9bed..f27e3ea 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -493,6 +493,10 @@ bool CopyStringLiteral() if(IncomingBuffer[0] != '"') // stopped going for some reason other than a close quote { ErrorNum = -1; + if(IncomingBuffer[0] == '\n') + { + --CurLine; + } DynamicErrorText << "Malformed string literal on line " << CurLine; return false; } @@ -797,7 +801,8 @@ bool ImportBASIC(const char *Filename, Uint8 *Mem, int* Size) if(Length >= 256) { ErrorNum = -1; - DynamicErrorText << "Overly long line at line " << CurLine; + /* CurLine - 1 because we've incremented it on seeing '\n' */ + DynamicErrorText << "Overly long line at line " << CurLine - 1; break; } Memory[LengthAddr] = static_cast(Length); --- src/BASIC.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BASIC.cpp b/src/BASIC.cpp index dec9bed..f27e3ea 100644 --- a/src/BASIC.cpp +++ b/src/BASIC.cpp @@ -493,6 +493,10 @@ bool CopyStringLiteral() if(IncomingBuffer[0] != '"') // stopped going for some reason other than a close quote { ErrorNum = -1; + if(IncomingBuffer[0] == '\n') + { + --CurLine; + } DynamicErrorText << "Malformed string literal on line " << CurLine; return false; } @@ -797,7 +801,8 @@ bool ImportBASIC(const char *Filename, Uint8 *Mem, int* Size) if(Length >= 256) { ErrorNum = -1; - DynamicErrorText << "Overly long line at line " << CurLine; + /* CurLine - 1 because we've incremented it on seeing '\n' */ + DynamicErrorText << "Overly long line at line " << CurLine - 1; break; } Memory[LengthAddr] = static_cast(Length); From 9ec41f5043738841c24b424cc1dacd924fbcb731 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 19:35:50 +0100 Subject: [PATCH 019/144] Document PUTBASIC line number fix in README.md diff --git a/README.md b/README.md index 036369a..969cffd 100644 --- a/README.md +++ b/README.md @@ -706,6 +706,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed tokenisation of BASIC pseudo-variables in some cases. Fixed incorrect line number for errors inside macros with blank lines inside them. + Fixed incorrect line numbers from PUTBASIC in some cases. TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 036369a..969cffd 100644 --- a/README.md +++ b/README.md @@ -706,6 +706,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed tokenisation of BASIC pseudo-variables in some cases. Fixed incorrect line number for errors inside macros with blank lines inside them. + Fixed incorrect line numbers from PUTBASIC in some cases. TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) From 04a622183f15a7c494a34581175b3bd9a8ff1791 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Mon, 5 Apr 2021 19:45:16 +0100 Subject: [PATCH 020/144] Thank Richard for advice on pseudo-variables. diff --git a/README.md b/README.md index 969cffd..519e61d 100644 --- a/README.md +++ b/README.md @@ -704,6 +704,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre as 0 (thanks to mungre for this fix). Allowed "-h" and "-help" options to see help. Fixed tokenisation of BASIC pseudo-variables in some cases. + (Thanks to Richard Russell for advice on this.) Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 969cffd..519e61d 100644 --- a/README.md +++ b/README.md @@ -704,6 +704,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre as 0 (thanks to mungre for this fix). Allowed "-h" and "-help" options to see help. Fixed tokenisation of BASIC pseudo-variables in some cases. + (Thanks to Richard Russell for advice on this.) Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. From 9ea20f55dc7d1ae11eca6e6ad6b20fd9fc3a617f Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Tue, 6 Apr 2021 16:42:28 +0100 Subject: [PATCH 021/144] Update .editorconfig with tab width 4 I infer this is correct from looking at how the existing source code is formatted; with this value things line up nicely. diff --git a/.editorconfig b/.editorconfig index 57b3dbb..73f0f5f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,4 @@ insert_final_newline = true [*.{c,h,cpp}] indent_style = tab +tab_width = 4 --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 57b3dbb..73f0f5f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,4 @@ insert_final_newline = true [*.{c,h,cpp}] indent_style = tab +tab_width = 4 From 82ae5d24c3d3cf864ed2d07c47a1650ef48f4d2c Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Tue, 6 Apr 2021 16:48:45 +0100 Subject: [PATCH 022/144] Factor out error filename/line number formatting diff --git a/src/asmexception.cpp b/src/asmexception.cpp index 6738542..7307480 100644 --- a/src/asmexception.cpp +++ b/src/asmexception.cpp @@ -24,7 +24,6 @@ #include #include -#include #include "asmexception.h" #include "globaldata.h" @@ -89,14 +88,5 @@ void AsmException_SyntaxError::Print() const /*************************************************************************************************/ std::string AsmException_SyntaxError::ErrorLocation( size_t i ) const { - std::stringstream s; - if ( GlobalData::Instance().UseVisualCppErrorFormat() ) - { - s << m_filename[ i ] << "(" << m_lineNumber[ i ] << ")"; - } - else - { - s << m_filename[ i ] << ":" << m_lineNumber[ i ]; - } - return s.str(); + return StringUtils::FormattedErrorLocation( m_filename[ i ], m_lineNumber[ i ] ); } diff --git a/src/stringutils.cpp b/src/stringutils.cpp index 58b9174..bd840f0 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -21,6 +21,8 @@ /*************************************************************************************************/ #include +#include +#include "globaldata.h" #include "stringutils.h" using namespace std; @@ -91,4 +93,32 @@ bool EatWhitespace( const string& line, size_t& column ) +/*************************************************************************************************/ +/** + FormttedErrorLocation() + + Return an error location formatted according to the command line options. + + @param filename Filename + @param lineNumber Line number + + @return string Error location string +*/ +/*************************************************************************************************/ +std::string FormattedErrorLocation( const std::string& filename, int lineNumber ) +{ + std::stringstream s; + if ( GlobalData::Instance().UseVisualCppErrorFormat() ) + { + s << filename << "(" << lineNumber << ")"; + } + else + { + s << filename << ":" << lineNumber; + } + return s.str(); +} + + + } // namespace StringUtils diff --git a/src/stringutils.h b/src/stringutils.h index 30ffa57..6852a3d 100644 --- a/src/stringutils.h +++ b/src/stringutils.h @@ -30,6 +30,7 @@ namespace StringUtils { void ExpandTabsToSpaces( std::string& line, size_t tabWidth ); bool EatWhitespace( const std::string& line, size_t& column ); + std::string FormattedErrorLocation ( const std::string& filename, int lineNumber ); } --- src/asmexception.cpp | 12 +----------- src/stringutils.cpp | 30 ++++++++++++++++++++++++++++++ src/stringutils.h | 1 + 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/asmexception.cpp b/src/asmexception.cpp index 6738542..7307480 100644 --- a/src/asmexception.cpp +++ b/src/asmexception.cpp @@ -24,7 +24,6 @@ #include #include -#include #include "asmexception.h" #include "globaldata.h" @@ -89,14 +88,5 @@ void AsmException_SyntaxError::Print() const /*************************************************************************************************/ std::string AsmException_SyntaxError::ErrorLocation( size_t i ) const { - std::stringstream s; - if ( GlobalData::Instance().UseVisualCppErrorFormat() ) - { - s << m_filename[ i ] << "(" << m_lineNumber[ i ] << ")"; - } - else - { - s << m_filename[ i ] << ":" << m_lineNumber[ i ]; - } - return s.str(); + return StringUtils::FormattedErrorLocation( m_filename[ i ], m_lineNumber[ i ] ); } diff --git a/src/stringutils.cpp b/src/stringutils.cpp index 58b9174..bd840f0 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -21,6 +21,8 @@ /*************************************************************************************************/ #include +#include +#include "globaldata.h" #include "stringutils.h" using namespace std; @@ -91,4 +93,32 @@ bool EatWhitespace( const string& line, size_t& column ) +/*************************************************************************************************/ +/** + FormttedErrorLocation() + + Return an error location formatted according to the command line options. + + @param filename Filename + @param lineNumber Line number + + @return string Error location string +*/ +/*************************************************************************************************/ +std::string FormattedErrorLocation( const std::string& filename, int lineNumber ) +{ + std::stringstream s; + if ( GlobalData::Instance().UseVisualCppErrorFormat() ) + { + s << filename << "(" << lineNumber << ")"; + } + else + { + s << filename << ":" << lineNumber; + } + return s.str(); +} + + + } // namespace StringUtils diff --git a/src/stringutils.h b/src/stringutils.h index 30ffa57..6852a3d 100644 --- a/src/stringutils.h +++ b/src/stringutils.h @@ -30,6 +30,7 @@ namespace StringUtils { void ExpandTabsToSpaces( std::string& line, size_t tabWidth ); bool EatWhitespace( const std::string& line, size_t& column ); + std::string FormattedErrorLocation ( const std::string& filename, int lineNumber ); } From 4560f3d54abd163d16e317b06042d9abb1f69a35 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Tue, 6 Apr 2021 16:52:37 +0100 Subject: [PATCH 023/144] Add FILELINE$ Tweaked version of tricky's code from https://stardot.org.uk/forums/viewtopic.php?p=280427#p280427 --- src/commands.cpp | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 7b393fc..edbb5d9 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -31,6 +31,7 @@ #include "lineparser.h" #include "globaldata.h" #include "objectcode.h" +#include "stringutils.h" #include "symboltable.h" #include "sourcefile.h" #include "asmexception.h" @@ -1534,29 +1535,44 @@ void LineParser::HandlePrint() } else { - // print in dec + StringUtils::EatWhitespace( m_line, m_column ); + const char* filelineKeyword = "FILELINE$"; + const int filelineKeywordLength = 9; - double value; - - try + if ( !strncmp( m_line.c_str() + m_column, filelineKeyword, filelineKeywordLength ) ) { - value = EvaluateExpression(); + if ( !GlobalData::Instance().IsFirstPass() ) + { + cout << StringUtils::FormattedErrorLocation( m_sourceCode->GetFilename(), m_sourceCode->GetLineNumber() ); + } + m_column += filelineKeywordLength ; } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) + else { - if ( GlobalData::Instance().IsFirstPass() ) + // print in dec + + double value; + + try { - value = 0.0; + value = EvaluateExpression(); } - else + catch ( AsmException_SyntaxError_SymbolNotDefined& ) { - throw; + if ( GlobalData::Instance().IsFirstPass() ) + { + value = 0.0; + } + else + { + throw; + } } - } - if ( GlobalData::Instance().IsSecondPass() ) - { - cout << value << " "; + if ( GlobalData::Instance().IsSecondPass() ) + { + cout << value << " "; + } } } } From 4c9dee7a1bfcd1df34822cf2806617971896065f Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Tue, 6 Apr 2021 17:24:00 +0100 Subject: [PATCH 024/144] Add CALLSTACK$ Tweaked version of tricky's code from https://stardot.org.uk/forums/viewtopic.php?p=280427#p280427 diff --git a/src/commands.cpp b/src/commands.cpp index edbb5d9..0d4e76c 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -618,7 +618,7 @@ void LineParser::HandleInclude() cerr << "Including file " << filename << endl; } - SourceFile input( filename.c_str() ); + SourceFile input( filename.c_str(), m_sourceCode ); input.Process(); } @@ -1538,6 +1538,8 @@ void LineParser::HandlePrint() StringUtils::EatWhitespace( m_line, m_column ); const char* filelineKeyword = "FILELINE$"; const int filelineKeywordLength = 9; + const char* callstackKeyword = "CALLSTACK$"; + const int callstackKeywordLength = 10; if ( !strncmp( m_line.c_str() + m_column, filelineKeyword, filelineKeywordLength ) ) { @@ -1547,6 +1549,18 @@ void LineParser::HandlePrint() } m_column += filelineKeywordLength ; } + else if ( !strncmp( m_line.c_str() + m_column, callstackKeyword, callstackKeywordLength ) ) + { + if ( !GlobalData::Instance().IsFirstPass() ) + { + cout << StringUtils::FormattedErrorLocation( m_sourceCode->GetFilename(), m_sourceCode->GetLineNumber() ); + for ( const SourceCode* s = m_sourceCode->GetParent(); s; s = s->GetParent() ) + { + cout << endl << StringUtils::FormattedErrorLocation( s->GetFilename(), s->GetLineNumber() ); + } + } + m_column += callstackKeywordLength; + } else { // print in dec diff --git a/src/macro.cpp b/src/macro.cpp index 2045787..9e936b0 100644 --- a/src/macro.cpp +++ b/src/macro.cpp @@ -60,7 +60,7 @@ Macro::Macro( const string& filename, int lineNumber ) */ /*************************************************************************************************/ MacroInstance::MacroInstance( const Macro* macro, const SourceCode* sourceCode ) - : SourceCode( macro->GetFilename(), macro->GetLineNumber() ), + : SourceCode( macro->GetFilename(), macro->GetLineNumber(), sourceCode ), m_stream( macro->GetBody() ) //,m_macro( macro ) { diff --git a/src/main.cpp b/src/main.cpp index d96d3fd..f92d4ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -282,7 +282,7 @@ int main( int argc, char* argv[] ) ObjectCode::Instance().InitialisePass(); GlobalData::Instance().ResetForId(); beebasm_srand( static_cast< unsigned long >( randomSeed ) ); - SourceFile input( pInputFile ); + SourceFile input( pInputFile, 0 ); input.Process(); } } diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 6965a21..7e14e9f 100644 --- a/src/sourcecode.cpp +++ b/src/sourcecode.cpp @@ -44,12 +44,14 @@ using namespace std; Constructor for SourceCode - @param pFilename Filename of source file to open + @param filename Filename of source file to open + @param lineNumber Line number + @param parent Parent SourceCode object (or null) The supplied file will be opened. If there is a problem, an AsmException will be thrown. */ /*************************************************************************************************/ -SourceCode::SourceCode( const string& filename, int lineNumber ) +SourceCode::SourceCode( const string& filename, int lineNumber, const SourceCode* parent ) : m_forStackPtr( 0 ), m_initialForStackPtr( 0 ), m_ifStackPtr( 0 ), @@ -57,6 +59,7 @@ SourceCode::SourceCode( const string& filename, int lineNumber ) m_currentMacro( NULL ), m_filename( filename ), m_lineNumber( lineNumber ), + m_parent( parent ), m_lineStartPointer( 0 ) { } diff --git a/src/sourcecode.h b/src/sourcecode.h index dc12562..9f7f4a6 100644 --- a/src/sourcecode.h +++ b/src/sourcecode.h @@ -35,7 +35,7 @@ public: // Constructor/destructor - SourceCode( const std::string& filename, int lineNumber ); + SourceCode( const std::string& filename, int lineNumber, const SourceCode* parent ); ~SourceCode(); // Process the file @@ -46,6 +46,7 @@ public: inline const std::string& GetFilename() const { return m_filename; } inline int GetLineNumber() const { return m_lineNumber; } + inline const SourceCode*GetParent() const { return m_parent; } inline int GetLineStartPointer() const { return m_lineStartPointer; } virtual std::istream& GetLine( std::string& lineFromFile ) = 0; @@ -138,6 +139,7 @@ protected: std::string m_filename; int m_lineNumber; + const SourceCode* m_parent; int m_lineStartPointer; }; diff --git a/src/sourcefile.cpp b/src/sourcefile.cpp index 7fba643..7db14e3 100644 --- a/src/sourcefile.cpp +++ b/src/sourcefile.cpp @@ -44,13 +44,14 @@ using namespace std; Constructor for SourceFile - @param pFilename Filename of source file to open + @param filename Filename of source file to open + @param parent Parent SourceCode object The supplied file will be opened. If there is a problem, an AsmException will be thrown. */ /*************************************************************************************************/ -SourceFile::SourceFile( const string& filename ) - : SourceCode( filename, 1 ) +SourceFile::SourceFile( const string& filename, const SourceCode* parent ) + : SourceCode( filename, 1, parent ) { // we have to open in binary, due to a bug in MinGW which means that calling // tellg() on a text-mode file ruins the file pointer! diff --git a/src/sourcefile.h b/src/sourcefile.h index 4e7c17d..8972a60 100644 --- a/src/sourcefile.h +++ b/src/sourcefile.h @@ -36,7 +36,7 @@ public: // Constructor/destructor (RAII class) - explicit SourceFile( const std::string& filename ); + SourceFile( const std::string& filename, const SourceCode* parent ); virtual ~SourceFile(); virtual void Process(); --- src/commands.cpp | 16 +++++++++++++++- src/macro.cpp | 2 +- src/main.cpp | 2 +- src/sourcecode.cpp | 7 +++++-- src/sourcecode.h | 4 +++- src/sourcefile.cpp | 7 ++++--- src/sourcefile.h | 2 +- 7 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index edbb5d9..0d4e76c 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -618,7 +618,7 @@ void LineParser::HandleInclude() cerr << "Including file " << filename << endl; } - SourceFile input( filename.c_str() ); + SourceFile input( filename.c_str(), m_sourceCode ); input.Process(); } @@ -1538,6 +1538,8 @@ void LineParser::HandlePrint() StringUtils::EatWhitespace( m_line, m_column ); const char* filelineKeyword = "FILELINE$"; const int filelineKeywordLength = 9; + const char* callstackKeyword = "CALLSTACK$"; + const int callstackKeywordLength = 10; if ( !strncmp( m_line.c_str() + m_column, filelineKeyword, filelineKeywordLength ) ) { @@ -1547,6 +1549,18 @@ void LineParser::HandlePrint() } m_column += filelineKeywordLength ; } + else if ( !strncmp( m_line.c_str() + m_column, callstackKeyword, callstackKeywordLength ) ) + { + if ( !GlobalData::Instance().IsFirstPass() ) + { + cout << StringUtils::FormattedErrorLocation( m_sourceCode->GetFilename(), m_sourceCode->GetLineNumber() ); + for ( const SourceCode* s = m_sourceCode->GetParent(); s; s = s->GetParent() ) + { + cout << endl << StringUtils::FormattedErrorLocation( s->GetFilename(), s->GetLineNumber() ); + } + } + m_column += callstackKeywordLength; + } else { // print in dec diff --git a/src/macro.cpp b/src/macro.cpp index 2045787..9e936b0 100644 --- a/src/macro.cpp +++ b/src/macro.cpp @@ -60,7 +60,7 @@ Macro::Macro( const string& filename, int lineNumber ) */ /*************************************************************************************************/ MacroInstance::MacroInstance( const Macro* macro, const SourceCode* sourceCode ) - : SourceCode( macro->GetFilename(), macro->GetLineNumber() ), + : SourceCode( macro->GetFilename(), macro->GetLineNumber(), sourceCode ), m_stream( macro->GetBody() ) //,m_macro( macro ) { diff --git a/src/main.cpp b/src/main.cpp index d96d3fd..f92d4ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -282,7 +282,7 @@ int main( int argc, char* argv[] ) ObjectCode::Instance().InitialisePass(); GlobalData::Instance().ResetForId(); beebasm_srand( static_cast< unsigned long >( randomSeed ) ); - SourceFile input( pInputFile ); + SourceFile input( pInputFile, 0 ); input.Process(); } } diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 6965a21..7e14e9f 100644 --- a/src/sourcecode.cpp +++ b/src/sourcecode.cpp @@ -44,12 +44,14 @@ using namespace std; Constructor for SourceCode - @param pFilename Filename of source file to open + @param filename Filename of source file to open + @param lineNumber Line number + @param parent Parent SourceCode object (or null) The supplied file will be opened. If there is a problem, an AsmException will be thrown. */ /*************************************************************************************************/ -SourceCode::SourceCode( const string& filename, int lineNumber ) +SourceCode::SourceCode( const string& filename, int lineNumber, const SourceCode* parent ) : m_forStackPtr( 0 ), m_initialForStackPtr( 0 ), m_ifStackPtr( 0 ), @@ -57,6 +59,7 @@ SourceCode::SourceCode( const string& filename, int lineNumber ) m_currentMacro( NULL ), m_filename( filename ), m_lineNumber( lineNumber ), + m_parent( parent ), m_lineStartPointer( 0 ) { } diff --git a/src/sourcecode.h b/src/sourcecode.h index dc12562..9f7f4a6 100644 --- a/src/sourcecode.h +++ b/src/sourcecode.h @@ -35,7 +35,7 @@ class SourceCode // Constructor/destructor - SourceCode( const std::string& filename, int lineNumber ); + SourceCode( const std::string& filename, int lineNumber, const SourceCode* parent ); ~SourceCode(); // Process the file @@ -46,6 +46,7 @@ class SourceCode inline const std::string& GetFilename() const { return m_filename; } inline int GetLineNumber() const { return m_lineNumber; } + inline const SourceCode*GetParent() const { return m_parent; } inline int GetLineStartPointer() const { return m_lineStartPointer; } virtual std::istream& GetLine( std::string& lineFromFile ) = 0; @@ -138,6 +139,7 @@ class SourceCode std::string m_filename; int m_lineNumber; + const SourceCode* m_parent; int m_lineStartPointer; }; diff --git a/src/sourcefile.cpp b/src/sourcefile.cpp index 7fba643..7db14e3 100644 --- a/src/sourcefile.cpp +++ b/src/sourcefile.cpp @@ -44,13 +44,14 @@ using namespace std; Constructor for SourceFile - @param pFilename Filename of source file to open + @param filename Filename of source file to open + @param parent Parent SourceCode object The supplied file will be opened. If there is a problem, an AsmException will be thrown. */ /*************************************************************************************************/ -SourceFile::SourceFile( const string& filename ) - : SourceCode( filename, 1 ) +SourceFile::SourceFile( const string& filename, const SourceCode* parent ) + : SourceCode( filename, 1, parent ) { // we have to open in binary, due to a bug in MinGW which means that calling // tellg() on a text-mode file ruins the file pointer! diff --git a/src/sourcefile.h b/src/sourcefile.h index 4e7c17d..8972a60 100644 --- a/src/sourcefile.h +++ b/src/sourcefile.h @@ -36,7 +36,7 @@ class SourceFile : public SourceCode // Constructor/destructor (RAII class) - explicit SourceFile( const std::string& filename ); + SourceFile( const std::string& filename, const SourceCode* parent ); virtual ~SourceFile(); virtual void Process(); From 4aa5ff36211b6d062fafe207de3e6b927fa8f21f Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Tue, 6 Apr 2021 17:28:07 +0100 Subject: [PATCH 025/144] Document FILELINE$ and CALLSTACK$ diff --git a/README.md b/README.md index 519e61d..07041d0 100644 --- a/README.md +++ b/README.md @@ -383,6 +383,8 @@ Examples: PRINT "Value of label 'start' =", ~start PRINT "numdots =", numdots, "dottable size =", dotend-dotstart ``` + +You can use `FILELINE$` in PRINT commands to show the current file and line number. `CALLSTACK$` will do the same but for all the parent macro and include files as well. See `examples/filelinecallstackdemo.6502` for an example of their use. `ERROR "message"` @@ -708,7 +710,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. - TODO: OTHER CHANGES + Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/examples/filelinecallstackdemo.6502 b/examples/filelinecallstackdemo.6502 new file mode 100644 index 0000000..fe441e8 --- /dev/null +++ b/examples/filelinecallstackdemo.6502 @@ -0,0 +1,27 @@ +org &2000 + +macro foo n + bar n-1 +endmacro + +macro bar n + lda #n + print "Current call stack:", CALLSTACK$, " (end)" +endmacro + +.start + ldy #42 + print "Current location:", FILELINE$ + ldx #0 +.loop + foo 25 + sta &3000,x + iny + inx + cpx #4 + bne loop + rts + +.end + +save "test", start, end --- README.md | 4 +++- examples/filelinecallstackdemo.6502 | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 examples/filelinecallstackdemo.6502 diff --git a/README.md b/README.md index 519e61d..07041d0 100644 --- a/README.md +++ b/README.md @@ -383,6 +383,8 @@ Examples: PRINT "Value of label 'start' =", ~start PRINT "numdots =", numdots, "dottable size =", dotend-dotstart ``` + +You can use `FILELINE$` in PRINT commands to show the current file and line number. `CALLSTACK$` will do the same but for all the parent macro and include files as well. See `examples/filelinecallstackdemo.6502` for an example of their use. `ERROR "message"` @@ -708,7 +710,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. - TODO: OTHER CHANGES + Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/examples/filelinecallstackdemo.6502 b/examples/filelinecallstackdemo.6502 new file mode 100644 index 0000000..fe441e8 --- /dev/null +++ b/examples/filelinecallstackdemo.6502 @@ -0,0 +1,27 @@ +org &2000 + +macro foo n + bar n-1 +endmacro + +macro bar n + lda #n + print "Current call stack:", CALLSTACK$, " (end)" +endmacro + +.start + ldy #42 + print "Current location:", FILELINE$ + ldx #0 +.loop + foo 25 + sta &3000,x + iny + inx + cpx #4 + bne loop + rts + +.end + +save "test", start, end From 67eef8135bce3f932978b193c860d85f3dc868c2 Mon Sep 17 00:00:00 2001 From: Steven Flintham Date: Thu, 8 Apr 2021 02:12:08 +0100 Subject: [PATCH 026/144] Add -writes option Based on tricky's code here: https://stardot.org.uk/forums/viewtopic.php?p=316353#p316353 diff --git a/README.md b/README.md index 519e61d..cd2c7fd 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,10 @@ If specified, this sets the disc option of the generated disc image (i.e. the `* If specified, this sets the disc title of the generated disc image (i.e. the string set by `*TITLE`) to the value specified. +`-writes ` + +If specified, this sets the number of writes for the generated disc image (i.e. the number shown next to the title in the disc catalogue) to the value specified. + `-di ` If specified, BeebAsm will use this disc image as a template for the new disc image, rather than creating a new blank one. This is useful if you have a BASIC loader which you want to run before your executable. Note this cannot be the same as the `-do` filename! @@ -708,6 +712,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. + Added -writes option (thanks to tricky for this) TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) diff --git a/demo.ssd b/demo.ssd index 2c58e7f..51ce2d2 100644 Binary files a/demo.ssd and b/demo.ssd differ diff --git a/src/discimage.cpp b/src/discimage.cpp index bbed4b2..87e3393 100644 --- a/src/discimage.cpp +++ b/src/discimage.cpp @@ -112,6 +112,7 @@ DiscImage::DiscImage( const char* pOutput, const char* pInput ) // generate a blank catalog memset( m_aCatalog, 0, 0x200 ); + m_aCatalog[ 0x104 ] = GlobalData::Instance().GetDiscWrites(); m_aCatalog[ 0x106 ] = 0x03 | ( ( GlobalData::Instance().GetDiscOption() & 3 ) << 4); m_aCatalog[ 0x107 ] = 0x20; diff --git a/src/globaldata.cpp b/src/globaldata.cpp index b68e0b3..9fc0457 100644 --- a/src/globaldata.cpp +++ b/src/globaldata.cpp @@ -76,6 +76,7 @@ GlobalData::GlobalData() m_pOutputFile( NULL ), m_numAnonSaves( 0 ), m_discOption( 0 ), + m_discWrites( 0 ), m_assemblyTime( time( NULL ) ), m_bRequireDistinctOpcodes( false ), m_bUseVisualCppErrorFormat( false ) diff --git a/src/globaldata.h b/src/globaldata.h index 25a4f98..4e6afbf 100644 --- a/src/globaldata.h +++ b/src/globaldata.h @@ -49,6 +49,7 @@ public: inline void SetOutputFile( const char* p ) { m_pOutputFile = p; } inline void IncNumAnonSaves() { m_numAnonSaves++; } inline void SetDiscOption( int opt ) { m_discOption = opt; } + inline void SetDiscWrites( int num ) { m_discWrites = num; } inline void SetDiscTitle( const std::string& t ) { m_discTitle = t; } inline void SetRequireDistinctOpcodes ( bool b ) @@ -68,6 +69,7 @@ public: inline const char* GetOutputFile() const { return m_pOutputFile; } inline int GetNumAnonSaves() const { return m_numAnonSaves; } inline int GetDiscOption() const { return m_discOption; } + inline int GetDiscWrites() const { return m_discWrites; } inline const std::string& GetDiscTitle() const { return m_discTitle; } inline time_t GetAssemblyTime() const { return m_assemblyTime; } @@ -91,6 +93,7 @@ private: const char* m_pOutputFile; int m_numAnonSaves; int m_discOption; + int m_discWrites; std::string m_discTitle; time_t m_assemblyTime; bool m_bRequireDistinctOpcodes; diff --git a/src/main.cpp b/src/main.cpp index d96d3fd..c36e2a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,6 +73,7 @@ int main( int argc, char* argv[] ) WAITING_FOR_BOOT_FILENAME, WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, + WAITING_FOR_DISC_WRITES, WAITING_FOR_SYMBOL } state = READY; @@ -118,6 +119,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_DISC_TITLE; } + else if ( strcmp( argv[i], "-writes" ) == 0 ) + { + state = WAITING_FOR_DISC_WRITES; + } else if ( strcmp( argv[i], "-w" ) == 0 ) { GlobalData::Instance().SetRequireDistinctOpcodes( true ); @@ -151,6 +156,7 @@ int main( int argc, char* argv[] ) cout << " -boot Specify a filename to be run by !BOOT on a new disc image" << endl; cout << " -opt Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title Specify the title for the generated disc image" << endl; + cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; cout << " -v Verbose output" << endl; cout << " -d Dump all global symbols after assembly" << endl; cout << " -w Require whitespace between opcodes and labels" << endl; @@ -221,6 +227,12 @@ int main( int argc, char* argv[] ) state = READY; break; + case WAITING_FOR_DISC_WRITES: + + GlobalData::Instance().SetDiscWrites( std::strtol( argv[i], NULL, 10 ) ); + state = READY; + break; + case WAITING_FOR_SYMBOL: if ( ! SymbolTable::Instance().AddCommandLineSymbol( argv[i] ) ) --- README.md | 5 +++++ demo.ssd | Bin 3072 -> 3072 bytes src/discimage.cpp | 1 + src/globaldata.cpp | 1 + src/globaldata.h | 3 +++ src/main.cpp | 12 ++++++++++++ 6 files changed, 22 insertions(+) diff --git a/README.md b/README.md index 519e61d..cd2c7fd 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,10 @@ If specified, this sets the disc option of the generated disc image (i.e. the `* If specified, this sets the disc title of the generated disc image (i.e. the string set by `*TITLE`) to the value specified. +`-writes <n>` + +If specified, this sets the number of writes for the generated disc image (i.e. the number shown next to the title in the disc catalogue) to the value specified. + `-di <filename>` If specified, BeebAsm will use this disc image as a template for the new disc image, rather than creating a new blank one. This is useful if you have a BASIC loader which you want to run before your executable. Note this cannot be the same as the `-do` filename! @@ -708,6 +712,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. + Added -writes option (thanks to tricky for this) TODO: OTHER CHANGES 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) diff --git a/demo.ssd b/demo.ssd index 2c58e7fcaf9330be01585ddd2324e6c097a24bec..51ce2d20c2d45be03c1b02d221b66f411be7f66a 100644 GIT binary patch delta 22 dcmZpWXpmq}%g<LxN-Ubl{+Cf}Gb`h7E&x&?2TuS1 delta 22 ZcmZpWXpmrMfPjhYe;FAzvoikX0st(+1dIRx diff --git a/src/discimage.cpp b/src/discimage.cpp index bbed4b2..87e3393 100644 --- a/src/discimage.cpp +++ b/src/discimage.cpp @@ -112,6 +112,7 @@ DiscImage::DiscImage( const char* pOutput, const char* pInput ) // generate a blank catalog memset( m_aCatalog, 0, 0x200 ); + m_aCatalog[ 0x104 ] = GlobalData::Instance().GetDiscWrites(); m_aCatalog[ 0x106 ] = 0x03 | ( ( GlobalData::Instance().GetDiscOption() & 3 ) << 4); m_aCatalog[ 0x107 ] = 0x20; diff --git a/src/globaldata.cpp b/src/globaldata.cpp index b68e0b3..9fc0457 100644 --- a/src/globaldata.cpp +++ b/src/globaldata.cpp @@ -76,6 +76,7 @@ GlobalData::GlobalData() m_pOutputFile( NULL ), m_numAnonSaves( 0 ), m_discOption( 0 ), + m_discWrites( 0 ), m_assemblyTime( time( NULL ) ), m_bRequireDistinctOpcodes( false ), m_bUseVisualCppErrorFormat( false ) diff --git a/src/globaldata.h b/src/globaldata.h index 25a4f98..4e6afbf 100644 --- a/src/globaldata.h +++ b/src/globaldata.h @@ -49,6 +49,7 @@ class GlobalData inline void SetOutputFile( const char* p ) { m_pOutputFile = p; } inline void IncNumAnonSaves() { m_numAnonSaves++; } inline void SetDiscOption( int opt ) { m_discOption = opt; } + inline void SetDiscWrites( int num ) { m_discWrites = num; } inline void SetDiscTitle( const std::string& t ) { m_discTitle = t; } inline void SetRequireDistinctOpcodes ( bool b ) @@ -68,6 +69,7 @@ class GlobalData inline const char* GetOutputFile() const { return m_pOutputFile; } inline int GetNumAnonSaves() const { return m_numAnonSaves; } inline int GetDiscOption() const { return m_discOption; } + inline int GetDiscWrites() const { return m_discWrites; } inline const std::string& GetDiscTitle() const { return m_discTitle; } inline time_t GetAssemblyTime() const { return m_assemblyTime; } @@ -91,6 +93,7 @@ class GlobalData const char* m_pOutputFile; int m_numAnonSaves; int m_discOption; + int m_discWrites; std::string m_discTitle; time_t m_assemblyTime; bool m_bRequireDistinctOpcodes; diff --git a/src/main.cpp b/src/main.cpp index d96d3fd..c36e2a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,6 +73,7 @@ int main( int argc, char* argv[] ) WAITING_FOR_BOOT_FILENAME, WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, + WAITING_FOR_DISC_WRITES, WAITING_FOR_SYMBOL } state = READY; @@ -118,6 +119,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_DISC_TITLE; } + else if ( strcmp( argv[i], "-writes" ) == 0 ) + { + state = WAITING_FOR_DISC_WRITES; + } else if ( strcmp( argv[i], "-w" ) == 0 ) { GlobalData::Instance().SetRequireDistinctOpcodes( true ); @@ -151,6 +156,7 @@ int main( int argc, char* argv[] ) cout << " -boot <file> Specify a filename to be run by !BOOT on a new disc image" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; + cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; cout << " -v Verbose output" << endl; cout << " -d Dump all global symbols after assembly" << endl; cout << " -w Require whitespace between opcodes and labels" << endl; @@ -221,6 +227,12 @@ int main( int argc, char* argv[] ) state = READY; break; + case WAITING_FOR_DISC_WRITES: + + GlobalData::Instance().SetDiscWrites( std::strtol( argv[i], NULL, 10 ) ); + state = READY; + break; + case WAITING_FOR_SYMBOL: if ( ! SymbolTable::Instance().AddCommandLineSymbol( argv[i] ) ) From 3b473cca65290631efa3cb0364781c0dc63ace26 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Thu, 8 Apr 2021 02:43:15 +0100 Subject: [PATCH 027/144] Add -dd and -labels This is based on tricky's code at https://stardot.org.uk/forums/viewtopic.php?p=316353#p316353 diff --git a/README.md b/README.md index 929548c..4ce8805 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,14 @@ Use Visual C++-style error messages. Dumps all global symbols in Swift-compatible format after assembly. This is used internally by Swift, and is just documented for completeness. +`-dd` + +Dumps all global and local symbols in Swift-compatible format after assembly. + +`-labels <file>` + +Write the output of `-d` or `-dd` to the specified file instead of standard output. + `-w` If specified, there must be whitespace between opcodes and their labels. This introduces an incompatibility with the BBC BASIC assembler, which allows things like `ck_axy=&70:stack_axy` (i.e. `STA &70`), but makes it possible for macros to have names which begin with an opcode name, e.g.: @@ -715,7 +723,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) - Added -writes option (thanks to tricky for this) + Added -writes, -dd and -labels options (thanks to tricky for these) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/src/commands.cpp b/src/commands.cpp index 0d4e76c..3eb37cf 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -208,6 +208,8 @@ void LineParser::HandleDefineLabel() { throw AsmException_SyntaxError_SecondPassProblem( m_line, oldColumn ); } + + SymbolTable::Instance().AddLabel(symbolName); } if ( GlobalData::Instance().ShouldOutputAsm() ) diff --git a/src/main.cpp b/src/main.cpp index a51cba9..18f09b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,6 +62,7 @@ int main( int argc, char* argv[] ) const char* pOutputFile = NULL; const char* pDiscInputFile = NULL; const char* pDiscOutputFile = NULL; + const char* pLabelsOutputFile = NULL; enum STATES { @@ -74,11 +75,13 @@ int main( int argc, char* argv[] ) WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, WAITING_FOR_DISC_WRITES, - WAITING_FOR_SYMBOL + WAITING_FOR_SYMBOL, + WAITING_FOR_LABELS_FILE } state = READY; bool bDumpSymbols = false; + bool bDumpAllSymbols = false; GlobalData::Create(); SymbolTable::Create(); @@ -111,6 +114,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_BOOT_FILENAME; } + else if ( strcmp( argv[i], "-labels" ) == 0 ) + { + state = WAITING_FOR_LABELS_FILE; + } else if ( strcmp( argv[i], "-opt" ) == 0 ) { state = WAITING_FOR_DISC_OPTION; @@ -139,6 +146,10 @@ int main( int argc, char* argv[] ) { bDumpSymbols = true; } + else if ( strcmp( argv[i], "-dd" ) == 0 ) + { + bDumpAllSymbols = true; + } else if ( strcmp( argv[i], "-D" ) == 0 ) { state = WAITING_FOR_SYMBOL; @@ -154,11 +165,13 @@ int main( int argc, char* argv[] ) cout << " -di <file> Specify a disc image file to be added to" << endl; cout << " -do <file> Specify a disc image file to output" << endl; cout << " -boot <file> Specify a filename to be run by !BOOT on a new disc image" << endl; + cout << " -labels <file> Specify a filename to export any labels dumpted with -d or -dd to" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; cout << " -v Verbose output" << endl; cout << " -d Dump all global symbols after assembly" << endl; + cout << " -dd Dump all global and local symbols after assembly" << endl; cout << " -w Require whitespace between opcodes and labels" << endl; cout << " -vc Use Visual C++-style error messages" << endl; cout << " -D <sym>=<val> Define symbol prior to assembly" << endl; @@ -242,6 +255,12 @@ int main( int argc, char* argv[] ) } state = READY; break; + + case WAITING_FOR_LABELS_FILE: + + pLabelsOutputFile = argv[i]; + state = READY; + break; } } @@ -306,9 +325,9 @@ int main( int argc, char* argv[] ) delete pDiscIm; - if ( bDumpSymbols && exitCode == EXIT_SUCCESS ) + if ( (bDumpSymbols || bDumpAllSymbols) && exitCode == EXIT_SUCCESS ) { - SymbolTable::Instance().Dump(); + SymbolTable::Instance().Dump(bDumpSymbols, bDumpAllSymbols, pLabelsOutputFile); } if ( !GlobalData::Instance().IsSaved() && exitCode == EXIT_SUCCESS ) diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 7e14e9f..654fce1 100644 --- a/src/sourcecode.cpp +++ b/src/sourcecode.cpp @@ -218,6 +218,7 @@ void SourceCode::AddFor( const string& varName, m_forStack[ m_forStackPtr ].m_column = column; m_forStack[ m_forStackPtr ].m_lineNumber = m_lineNumber; + SymbolTable::Instance().PushFor(m_forStack[ m_forStackPtr ].m_varName, m_forStack[ m_forStackPtr ].m_current); m_forStackPtr++; } @@ -250,6 +251,7 @@ void SourceCode::OpenBrace( const string& line, int column ) m_forStack[ m_forStackPtr ].m_column = column; m_forStack[ m_forStackPtr ].m_lineNumber = m_lineNumber; + SymbolTable::Instance().PushBrace(); m_forStackPtr++; } @@ -283,6 +285,7 @@ void SourceCode::UpdateFor( const string& line, int column ) { // we have reached the end of the FOR SymbolTable::Instance().RemoveSymbol( thisFor.m_varName ); + SymbolTable::Instance().PopScope(); m_forStackPtr--; } else @@ -290,6 +293,8 @@ void SourceCode::UpdateFor( const string& line, int column ) // reloop SymbolTable::Instance().ChangeSymbol( thisFor.m_varName, thisFor.m_current ); SetFilePointer( thisFor.m_filePtr ); + SymbolTable::Instance().PopScope(); + SymbolTable::Instance().PushFor(thisFor.m_varName, thisFor.m_current); thisFor.m_count++; m_lineNumber = thisFor.m_lineNumber - 1; } @@ -328,6 +333,7 @@ void SourceCode::CloseBrace( const string& line, int column ) throw AsmException_SyntaxError_MismatchedBraces( line, column ); } + SymbolTable::Instance().PopScope(); m_forStackPtr--; } diff --git a/src/stringutils.cpp b/src/stringutils.cpp index bd840f0..cd9cc96 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -95,7 +95,7 @@ bool EatWhitespace( const string& line, size_t& column ) /*************************************************************************************************/ /** - FormttedErrorLocation() + FormattedErrorLocation() Return an error location formatted according to the command line options. diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 61b8324..114edf3 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -22,9 +22,12 @@ #include <cmath> #include <cstdio> +#include <fstream> #include <iostream> #include <sstream> +#include "globaldata.h" +#include "objectcode.h" #include "symboltable.h" #include "constants.h" @@ -76,6 +79,7 @@ void SymbolTable::Destroy() */ /*************************************************************************************************/ SymbolTable::SymbolTable() + : m_labelScopes( 0 ) { // Add any constant symbols here @@ -318,26 +322,47 @@ void SymbolTable::RemoveSymbol( const std::string& symbol ) Dumps all global symbols in the symbol table */ /*************************************************************************************************/ -void SymbolTable::Dump() const +void SymbolTable::Dump(bool global, bool all, const char * labels_file) const { + std::ofstream labels; + std::ostream & cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; + cout << "[{"; bool bFirst = true; - for ( map<string, Symbol>::const_iterator it = m_map.begin(); it != m_map.end(); ++it ) + if (global) { - const string& symbolName = it->first; - const Symbol& symbol = it->second; + for ( map<string, Symbol>::const_iterator it = m_map.begin(); it != m_map.end(); ++it ) + { + const string& symbolName = it->first; + const Symbol& symbol = it->second; + + if ( symbol.IsLabel() && + symbolName.find_first_of( '@' ) == string::npos ) + { + if ( !bFirst ) + { + cout << ","; + } + + cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + + bFirst = false; + } + } + } - if ( symbol.IsLabel() && - symbolName.find_first_of( '@' ) == string::npos ) + if (all) + { + for (const Label & label : m_labelList) { if ( !bFirst ) { cout << ","; } - cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + cout << "'" << label.m_identifier << "':" << label.m_addr << "L"; bFirst = false; } @@ -345,3 +370,53 @@ void SymbolTable::Dump() const cout << "}]" << endl; } + +void SymbolTable::PushBrace() +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + if (last_label.m_addr != addr) + { + std::ostringstream label; label << "._" << (m_labelScopes - last_label.m_scope); + last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); + last_label.m_addr = addr; + } + last_label.m_scope = m_labelScopes++; + m_labelStack.push_back(last_label); + } +} + +void SymbolTable::PushFor(std::string symbol, double value) +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + symbol = symbol.substr(0, symbol.find_first_of('@')); + std::ostringstream label; label << "._" << symbol << "_" << value; + last_label.m_identifier += label.str(); + last_label.m_addr = addr; + last_label.m_scope = m_labelScopes++; + m_labelStack.push_back(last_label); + } +} + +void SymbolTable::AddLabel(const std::string& symbol) +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; + last_label.m_addr = addr; + m_labelList.emplace_back(last_label); + } +} + +void SymbolTable::PopScope() +{ + if (GlobalData::Instance().IsSecondPass()) + { + m_labelStack.pop_back(); + last_label = m_labelStack.empty() ? Label() : m_labelStack.back(); + } +} diff --git a/src/symboltable.h b/src/symboltable.h index 0a71f8c..88038f7 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -27,6 +27,7 @@ #include <cstdlib> #include <map> #include <string> +#include <vector> class SymbolTable @@ -44,8 +45,12 @@ public: bool IsSymbolDefined( const std::string& symbol ) const; void RemoveSymbol( const std::string& symbol ); - void Dump() const; + void Dump(bool global, bool all, const char * labels_file) const; // labels_file == nullptr -> stdout + void PushBrace(); + void PushFor(std::string symbol, double value); + void AddLabel(const std::string & symbol); + void PopScope(); private: @@ -71,6 +76,17 @@ private: std::map<std::string, Symbol> m_map; static SymbolTable* m_gInstance; + + int m_labelScopes; + struct Label + { + int m_addr; + int m_scope; + std::string m_identifier; // "" -> using label from parent scope + Label(int addr = 0, int scope = 0, const std::string & identifier = "") : m_addr(addr), m_scope(scope), m_identifier(identifier) {} + } last_label; + std::vector<Label> m_labelStack; + std::vector<Label> m_labelList; }; --- README.md | 10 ++++- src/commands.cpp | 2 + src/main.cpp | 25 +++++++++++-- src/sourcecode.cpp | 6 +++ src/stringutils.cpp | 2 +- src/symboltable.cpp | 89 +++++++++++++++++++++++++++++++++++++++++---- src/symboltable.h | 18 ++++++++- 7 files changed, 139 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 929548c..4ce8805 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,14 @@ Use Visual C++-style error messages. Dumps all global symbols in Swift-compatible format after assembly. This is used internally by Swift, and is just documented for completeness. +`-dd` + +Dumps all global and local symbols in Swift-compatible format after assembly. + +`-labels <file>` + +Write the output of `-d` or `-dd` to the specified file instead of standard output. + `-w` If specified, there must be whitespace between opcodes and their labels. This introduces an incompatibility with the BBC BASIC assembler, which allows things like `ck_axy=&70:stack_axy` (i.e. `STA &70`), but makes it possible for macros to have names which begin with an opcode name, e.g.: @@ -715,7 +723,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) - Added -writes option (thanks to tricky for this) + Added -writes, -dd and -labels options (thanks to tricky for these) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/src/commands.cpp b/src/commands.cpp index 0d4e76c..3eb37cf 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -208,6 +208,8 @@ void LineParser::HandleDefineLabel() { throw AsmException_SyntaxError_SecondPassProblem( m_line, oldColumn ); } + + SymbolTable::Instance().AddLabel(symbolName); } if ( GlobalData::Instance().ShouldOutputAsm() ) diff --git a/src/main.cpp b/src/main.cpp index a51cba9..18f09b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,6 +62,7 @@ int main( int argc, char* argv[] ) const char* pOutputFile = NULL; const char* pDiscInputFile = NULL; const char* pDiscOutputFile = NULL; + const char* pLabelsOutputFile = NULL; enum STATES { @@ -74,11 +75,13 @@ int main( int argc, char* argv[] ) WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, WAITING_FOR_DISC_WRITES, - WAITING_FOR_SYMBOL + WAITING_FOR_SYMBOL, + WAITING_FOR_LABELS_FILE } state = READY; bool bDumpSymbols = false; + bool bDumpAllSymbols = false; GlobalData::Create(); SymbolTable::Create(); @@ -111,6 +114,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_BOOT_FILENAME; } + else if ( strcmp( argv[i], "-labels" ) == 0 ) + { + state = WAITING_FOR_LABELS_FILE; + } else if ( strcmp( argv[i], "-opt" ) == 0 ) { state = WAITING_FOR_DISC_OPTION; @@ -139,6 +146,10 @@ int main( int argc, char* argv[] ) { bDumpSymbols = true; } + else if ( strcmp( argv[i], "-dd" ) == 0 ) + { + bDumpAllSymbols = true; + } else if ( strcmp( argv[i], "-D" ) == 0 ) { state = WAITING_FOR_SYMBOL; @@ -154,11 +165,13 @@ int main( int argc, char* argv[] ) cout << " -di <file> Specify a disc image file to be added to" << endl; cout << " -do <file> Specify a disc image file to output" << endl; cout << " -boot <file> Specify a filename to be run by !BOOT on a new disc image" << endl; + cout << " -labels <file> Specify a filename to export any labels dumpted with -d or -dd to" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; cout << " -v Verbose output" << endl; cout << " -d Dump all global symbols after assembly" << endl; + cout << " -dd Dump all global and local symbols after assembly" << endl; cout << " -w Require whitespace between opcodes and labels" << endl; cout << " -vc Use Visual C++-style error messages" << endl; cout << " -D <sym>=<val> Define symbol prior to assembly" << endl; @@ -242,6 +255,12 @@ int main( int argc, char* argv[] ) } state = READY; break; + + case WAITING_FOR_LABELS_FILE: + + pLabelsOutputFile = argv[i]; + state = READY; + break; } } @@ -306,9 +325,9 @@ int main( int argc, char* argv[] ) delete pDiscIm; - if ( bDumpSymbols && exitCode == EXIT_SUCCESS ) + if ( (bDumpSymbols || bDumpAllSymbols) && exitCode == EXIT_SUCCESS ) { - SymbolTable::Instance().Dump(); + SymbolTable::Instance().Dump(bDumpSymbols, bDumpAllSymbols, pLabelsOutputFile); } if ( !GlobalData::Instance().IsSaved() && exitCode == EXIT_SUCCESS ) diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 7e14e9f..654fce1 100644 --- a/src/sourcecode.cpp +++ b/src/sourcecode.cpp @@ -218,6 +218,7 @@ void SourceCode::AddFor( const string& varName, m_forStack[ m_forStackPtr ].m_column = column; m_forStack[ m_forStackPtr ].m_lineNumber = m_lineNumber; + SymbolTable::Instance().PushFor(m_forStack[ m_forStackPtr ].m_varName, m_forStack[ m_forStackPtr ].m_current); m_forStackPtr++; } @@ -250,6 +251,7 @@ void SourceCode::OpenBrace( const string& line, int column ) m_forStack[ m_forStackPtr ].m_column = column; m_forStack[ m_forStackPtr ].m_lineNumber = m_lineNumber; + SymbolTable::Instance().PushBrace(); m_forStackPtr++; } @@ -283,6 +285,7 @@ void SourceCode::UpdateFor( const string& line, int column ) { // we have reached the end of the FOR SymbolTable::Instance().RemoveSymbol( thisFor.m_varName ); + SymbolTable::Instance().PopScope(); m_forStackPtr--; } else @@ -290,6 +293,8 @@ void SourceCode::UpdateFor( const string& line, int column ) // reloop SymbolTable::Instance().ChangeSymbol( thisFor.m_varName, thisFor.m_current ); SetFilePointer( thisFor.m_filePtr ); + SymbolTable::Instance().PopScope(); + SymbolTable::Instance().PushFor(thisFor.m_varName, thisFor.m_current); thisFor.m_count++; m_lineNumber = thisFor.m_lineNumber - 1; } @@ -328,6 +333,7 @@ void SourceCode::CloseBrace( const string& line, int column ) throw AsmException_SyntaxError_MismatchedBraces( line, column ); } + SymbolTable::Instance().PopScope(); m_forStackPtr--; } diff --git a/src/stringutils.cpp b/src/stringutils.cpp index bd840f0..cd9cc96 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -95,7 +95,7 @@ bool EatWhitespace( const string& line, size_t& column ) /*************************************************************************************************/ /** - FormttedErrorLocation() + FormattedErrorLocation() Return an error location formatted according to the command line options. diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 61b8324..114edf3 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -22,9 +22,12 @@ #include <cmath> #include <cstdio> +#include <fstream> #include <iostream> #include <sstream> +#include "globaldata.h" +#include "objectcode.h" #include "symboltable.h" #include "constants.h" @@ -76,6 +79,7 @@ void SymbolTable::Destroy() */ /*************************************************************************************************/ SymbolTable::SymbolTable() + : m_labelScopes( 0 ) { // Add any constant symbols here @@ -318,26 +322,47 @@ void SymbolTable::RemoveSymbol( const std::string& symbol ) Dumps all global symbols in the symbol table */ /*************************************************************************************************/ -void SymbolTable::Dump() const +void SymbolTable::Dump(bool global, bool all, const char * labels_file) const { + std::ofstream labels; + std::ostream & cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; + cout << "[{"; bool bFirst = true; - for ( map<string, Symbol>::const_iterator it = m_map.begin(); it != m_map.end(); ++it ) + if (global) { - const string& symbolName = it->first; - const Symbol& symbol = it->second; + for ( map<string, Symbol>::const_iterator it = m_map.begin(); it != m_map.end(); ++it ) + { + const string& symbolName = it->first; + const Symbol& symbol = it->second; + + if ( symbol.IsLabel() && + symbolName.find_first_of( '@' ) == string::npos ) + { + if ( !bFirst ) + { + cout << ","; + } + + cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + + bFirst = false; + } + } + } - if ( symbol.IsLabel() && - symbolName.find_first_of( '@' ) == string::npos ) + if (all) + { + for (const Label & label : m_labelList) { if ( !bFirst ) { cout << ","; } - cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + cout << "'" << label.m_identifier << "':" << label.m_addr << "L"; bFirst = false; } @@ -345,3 +370,53 @@ void SymbolTable::Dump() const cout << "}]" << endl; } + +void SymbolTable::PushBrace() +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + if (last_label.m_addr != addr) + { + std::ostringstream label; label << "._" << (m_labelScopes - last_label.m_scope); + last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); + last_label.m_addr = addr; + } + last_label.m_scope = m_labelScopes++; + m_labelStack.push_back(last_label); + } +} + +void SymbolTable::PushFor(std::string symbol, double value) +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + symbol = symbol.substr(0, symbol.find_first_of('@')); + std::ostringstream label; label << "._" << symbol << "_" << value; + last_label.m_identifier += label.str(); + last_label.m_addr = addr; + last_label.m_scope = m_labelScopes++; + m_labelStack.push_back(last_label); + } +} + +void SymbolTable::AddLabel(const std::string& symbol) +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; + last_label.m_addr = addr; + m_labelList.emplace_back(last_label); + } +} + +void SymbolTable::PopScope() +{ + if (GlobalData::Instance().IsSecondPass()) + { + m_labelStack.pop_back(); + last_label = m_labelStack.empty() ? Label() : m_labelStack.back(); + } +} diff --git a/src/symboltable.h b/src/symboltable.h index 0a71f8c..88038f7 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -27,6 +27,7 @@ #include <cstdlib> #include <map> #include <string> +#include <vector> class SymbolTable @@ -44,8 +45,12 @@ class SymbolTable bool IsSymbolDefined( const std::string& symbol ) const; void RemoveSymbol( const std::string& symbol ); - void Dump() const; + void Dump(bool global, bool all, const char * labels_file) const; // labels_file == nullptr -> stdout + void PushBrace(); + void PushFor(std::string symbol, double value); + void AddLabel(const std::string & symbol); + void PopScope(); private: @@ -71,6 +76,17 @@ class SymbolTable std::map<std::string, Symbol> m_map; static SymbolTable* m_gInstance; + + int m_labelScopes; + struct Label + { + int m_addr; + int m_scope; + std::string m_identifier; // "" -> using label from parent scope + Label(int addr = 0, int scope = 0, const std::string & identifier = "") : m_addr(addr), m_scope(scope), m_identifier(identifier) {} + } last_label; + std::vector<Label> m_labelStack; + std::vector<Label> m_labelList; }; From 54bd211b538aabbcb1b29fe566adbb78a5c7673f Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Thu, 8 Apr 2021 19:29:19 +0100 Subject: [PATCH 028/144] Remove minor C++11 feature use Seems a shame to break compatibility with C++98 just for this. Thanks to tricky for pointing this out. diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 114edf3..92d8993 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -355,14 +355,14 @@ void SymbolTable::Dump(bool global, bool all, const char * labels_file) const if (all) { - for (const Label & label : m_labelList) + for ( std::vector<Label>::const_iterator it = m_labelList.begin(); it != m_labelList.end(); ++it ) { if ( !bFirst ) { cout << ","; } - cout << "'" << label.m_identifier << "':" << label.m_addr << "L"; + cout << "'" << it->m_identifier << "':" << it->m_addr << "L"; bFirst = false; } @@ -376,14 +376,14 @@ void SymbolTable::PushBrace() if (GlobalData::Instance().IsSecondPass()) { int addr = ObjectCode::Instance().GetPC(); - if (last_label.m_addr != addr) + if (m_lastLabel.m_addr != addr) { - std::ostringstream label; label << "._" << (m_labelScopes - last_label.m_scope); - last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); - last_label.m_addr = addr; + std::ostringstream label; label << "._" << (m_labelScopes - m_lastLabel.m_scope); + m_lastLabel.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); + m_lastLabel.m_addr = addr; } - last_label.m_scope = m_labelScopes++; - m_labelStack.push_back(last_label); + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); } } @@ -394,10 +394,10 @@ void SymbolTable::PushFor(std::string symbol, double value) int addr = ObjectCode::Instance().GetPC(); symbol = symbol.substr(0, symbol.find_first_of('@')); std::ostringstream label; label << "._" << symbol << "_" << value; - last_label.m_identifier += label.str(); - last_label.m_addr = addr; - last_label.m_scope = m_labelScopes++; - m_labelStack.push_back(last_label); + m_lastLabel.m_identifier += label.str(); + m_lastLabel.m_addr = addr; + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); } } @@ -406,9 +406,9 @@ void SymbolTable::AddLabel(const std::string& symbol) if (GlobalData::Instance().IsSecondPass()) { int addr = ObjectCode::Instance().GetPC(); - last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; - last_label.m_addr = addr; - m_labelList.emplace_back(last_label); + m_lastLabel.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; + m_lastLabel.m_addr = addr; + m_labelList.push_back(m_lastLabel); } } @@ -417,6 +417,6 @@ void SymbolTable::PopScope() if (GlobalData::Instance().IsSecondPass()) { m_labelStack.pop_back(); - last_label = m_labelStack.empty() ? Label() : m_labelStack.back(); + m_lastLabel = m_labelStack.empty() ? Label() : m_labelStack.back(); } } diff --git a/src/symboltable.h b/src/symboltable.h index 88038f7..8fb66aa 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -84,7 +84,7 @@ private: int m_scope; std::string m_identifier; // "" -> using label from parent scope Label(int addr = 0, int scope = 0, const std::string & identifier = "") : m_addr(addr), m_scope(scope), m_identifier(identifier) {} - } last_label; + } m_lastLabel; std::vector<Label> m_labelStack; std::vector<Label> m_labelList; }; --- src/symboltable.cpp | 32 ++++++++++++++++---------------- src/symboltable.h | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 114edf3..92d8993 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -355,14 +355,14 @@ void SymbolTable::Dump(bool global, bool all, const char * labels_file) const if (all) { - for (const Label & label : m_labelList) + for ( std::vector<Label>::const_iterator it = m_labelList.begin(); it != m_labelList.end(); ++it ) { if ( !bFirst ) { cout << ","; } - cout << "'" << label.m_identifier << "':" << label.m_addr << "L"; + cout << "'" << it->m_identifier << "':" << it->m_addr << "L"; bFirst = false; } @@ -376,14 +376,14 @@ void SymbolTable::PushBrace() if (GlobalData::Instance().IsSecondPass()) { int addr = ObjectCode::Instance().GetPC(); - if (last_label.m_addr != addr) + if (m_lastLabel.m_addr != addr) { - std::ostringstream label; label << "._" << (m_labelScopes - last_label.m_scope); - last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); - last_label.m_addr = addr; + std::ostringstream label; label << "._" << (m_labelScopes - m_lastLabel.m_scope); + m_lastLabel.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + label.str(); + m_lastLabel.m_addr = addr; } - last_label.m_scope = m_labelScopes++; - m_labelStack.push_back(last_label); + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); } } @@ -394,10 +394,10 @@ void SymbolTable::PushFor(std::string symbol, double value) int addr = ObjectCode::Instance().GetPC(); symbol = symbol.substr(0, symbol.find_first_of('@')); std::ostringstream label; label << "._" << symbol << "_" << value; - last_label.m_identifier += label.str(); - last_label.m_addr = addr; - last_label.m_scope = m_labelScopes++; - m_labelStack.push_back(last_label); + m_lastLabel.m_identifier += label.str(); + m_lastLabel.m_addr = addr; + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); } } @@ -406,9 +406,9 @@ void SymbolTable::AddLabel(const std::string& symbol) if (GlobalData::Instance().IsSecondPass()) { int addr = ObjectCode::Instance().GetPC(); - last_label.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; - last_label.m_addr = addr; - m_labelList.emplace_back(last_label); + m_lastLabel.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; + m_lastLabel.m_addr = addr; + m_labelList.push_back(m_lastLabel); } } @@ -417,6 +417,6 @@ void SymbolTable::PopScope() if (GlobalData::Instance().IsSecondPass()) { m_labelStack.pop_back(); - last_label = m_labelStack.empty() ? Label() : m_labelStack.back(); + m_lastLabel = m_labelStack.empty() ? Label() : m_labelStack.back(); } } diff --git a/src/symboltable.h b/src/symboltable.h index 88038f7..8fb66aa 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -84,7 +84,7 @@ class SymbolTable int m_scope; std::string m_identifier; // "" -> using label from parent scope Label(int addr = 0, int scope = 0, const std::string & identifier = "") : m_addr(addr), m_scope(scope), m_identifier(identifier) {} - } last_label; + } m_lastLabel; std::vector<Label> m_labelStack; std::vector<Label> m_labelList; }; From ec52a02d24dedaec0bab22a2bfe30e03af2ec846 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Thu, 8 Apr 2021 19:32:05 +0100 Subject: [PATCH 029/144] Add missing #include <algorithm> Thanks to tricky for pointing this out. diff --git a/src/commands.cpp b/src/commands.cpp index 3eb37cf..ce630bb 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -22,6 +22,7 @@ */ /*************************************************************************************************/ +#include <algorithm> #include <iostream> #include <iomanip> #include <string> --- src/commands.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.cpp b/src/commands.cpp index 3eb37cf..ce630bb 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -22,6 +22,7 @@ */ /*************************************************************************************************/ +#include <algorithm> #include <iostream> #include <iomanip> #include <string> From 301bda610722c2a6340f42a0bd3ddfcc9a4c59c2 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Sat, 10 Apr 2021 16:03:38 +0100 Subject: [PATCH 030/144] Fix typo diff --git a/src/VS2010/BeebAsm.vcxproj b/src/VS2010/BeebAsm.vcxproj index 739c174..76a2292 100644 --- a/src/VS2010/BeebAsm.vcxproj +++ b/src/VS2010/BeebAsm.vcxproj @@ -12,15 +12,19 @@ </ItemGroup> <PropertyGroup Label="Globals"> <Keyword>Win32Proj</Keyword> + <ProjectGuid>{05CF19C4-5E11-22D4-C1AD-22BD997081FA}</ProjectGuid> + <WindowsTargetPlatformVersion>10.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>v142</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> diff --git a/src/main.cpp b/src/main.cpp index 18f09b9..a36417c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -165,7 +165,7 @@ int main( int argc, char* argv[] ) cout << " -di <file> Specify a disc image file to be added to" << endl; cout << " -do <file> Specify a disc image file to output" << endl; cout << " -boot <file> Specify a filename to be run by !BOOT on a new disc image" << endl; - cout << " -labels <file> Specify a filename to export any labels dumpted with -d or -dd to" << endl; + cout << " -labels <file> Specify a filename to export any labels dumped with -d or -dd to" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; --- src/VS2010/BeebAsm.vcxproj | 4 ++++ src/main.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VS2010/BeebAsm.vcxproj b/src/VS2010/BeebAsm.vcxproj index 739c174..76a2292 100644 --- a/src/VS2010/BeebAsm.vcxproj +++ b/src/VS2010/BeebAsm.vcxproj @@ -12,15 +12,19 @@ </ItemGroup> <PropertyGroup Label="Globals"> <Keyword>Win32Proj</Keyword> + <ProjectGuid>{05CF19C4-5E11-22D4-C1AD-22BD997081FA}</ProjectGuid> + <WindowsTargetPlatformVersion>10.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>v142</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> diff --git a/src/main.cpp b/src/main.cpp index 18f09b9..a36417c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -165,7 +165,7 @@ int main( int argc, char* argv[] ) cout << " -di <file> Specify a disc image file to be added to" << endl; cout << " -do <file> Specify a disc image file to output" << endl; cout << " -boot <file> Specify a filename to be run by !BOOT on a new disc image" << endl; - cout << " -labels <file> Specify a filename to export any labels dumpted with -d or -dd to" << endl; + cout << " -labels <file> Specify a filename to export any labels dumped with -d or -dd to" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; From d1693c26ba89670da382e6b1bce17d735eb35160 Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Thu, 17 Jun 2021 23:34:15 +0100 Subject: [PATCH 031/144] Add man page --- beebasm.1 | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 beebasm.1 diff --git a/beebasm.1 b/beebasm.1 new file mode 100644 index 0000000..a9a6085 --- /dev/null +++ b/beebasm.1 @@ -0,0 +1,50 @@ +.TH BEEBASM "1" "June 2021" "beebasm 1.09" "User Commands" +.SH NAME +beebasm \- portable 6502 assembler with BBC Micro style syntax +.SH SYNOPSIS +.B beebasm +.RI [ options ] +.RI -i +.RI <filename> +.SH DESCRIPTION +BeebAsm is a 6502 assembler designed specially for developing assembler programs for the BBC Micro. It uses syntax reminiscent of BBC BASIC's built-in assembler, and is able to output its object code directly into emulator-ready DFS disc images. +.SH OPTIONS +.SS "Possible options:" +.TP +\fB\-i\fR <file> +Specify source filename +.TP +\fB\-o\fR <file> +Specify output filename (when not specified by SAVE command) +.TP +\fB\-di\fR <file> +Specify a disc image file to be added to +.TP +\fB\-do\fR <file> +Specify a disc image file to output +.TP +\fB\-boot\fR <file> +Specify a filename to be run by !BOOT on a new disc image +.TP +\fB\-opt\fR <opt> +Specify the *OPT 4,n for the generated disc image +.HP +\fB\-title\fR <title> Specify the title for the generated disc image +.TP +\fB\-v\fR +Verbose output +.TP +\fB\-d\fR +Dump all global symbols after assembly +.TP +\fB\-w\fR +Require whitespace between opcodes and labels +.TP +\fB\-vc\fR +Use Visual C++\-style error messages +.HP +\fB\-D\fR <sym>=<val> Define symbol prior to assembly +.SH SEE ALSO +The full documentation can be found at +http://www.retrosoftware.co.uk/wiki/index.php/BeebAsm + From 420cb2a36e7ab1de1c4ffb62a05d6f5f7efc8d12 Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Thu, 17 Jun 2021 23:34:41 +0100 Subject: [PATCH 032/144] Add cmake configuration The current Makefile does not play nicely with packaging. --- CMakeLists.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7353b88 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) + +project(beebasm) + +add_compile_options(-Wall -W -Wcast-qual -Wshadow -Wcast-align -Wold-style-cast -Woverloaded-virtual) + +# Existing Makefile does a glob to find source files, so we do the same. +FILE(GLOB CPPSources src/*.cpp) + +add_executable(beebasm ${CPPSources}) + +install(TARGETS beebasm DESTINATION bin) +install(FILES ${CMAKE_SOURCE_DIR}/beebasm.1 DESTINATION share/man/man1) + +enable_testing() + +add_test(NAME Runs COMMAND ./beebasm -i demo.6502 -do demo.ssd -boot Code -v) From ea1cf4ad0fc897ec8eb67dbd6bf8e7c6a69ee67e Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Thu, 17 Jun 2021 23:57:09 +0100 Subject: [PATCH 033/144] Add workflow to check both build systems --- .github/workflows/actions.yml | 28 ++++++++++++++++++++++++++++ CMakeLists.txt | 4 ++++ 2 files changed, 32 insertions(+) create mode 100644 .github/workflows/actions.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..de01ca0 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,28 @@ +on: [push, pull_request] + +jobs: + cmake: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-16.04', 'ubuntu-18.04'] + cc: [ 'gcc', 'clang' ] + name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} + env: + CFLAGS: '-Wextra -Werror' + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cc }} + steps: + - uses: actions/checkout@v2 + - run: cmake . + - run: make + - run: make test + make: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-16.04', 'ubuntu-18.04'] + name: Compile via Makefile on ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - run: make -C src all diff --git a/CMakeLists.txt b/CMakeLists.txt index 7353b88..7906ba6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,14 @@ project(beebasm) add_compile_options(-Wall -W -Wcast-qual -Wshadow -Wcast-align -Wold-style-cast -Woverloaded-virtual) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + # Existing Makefile does a glob to find source files, so we do the same. FILE(GLOB CPPSources src/*.cpp) add_executable(beebasm ${CPPSources}) +target_link_libraries(beebasm stdc++ m) install(TARGETS beebasm DESTINATION bin) install(FILES ${CMAKE_SOURCE_DIR}/beebasm.1 DESTINATION share/man/man1) From c937951ca04f185edeb160ac5c4134dddcd21fc2 Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Sun, 20 Jun 2021 19:30:05 +0100 Subject: [PATCH 034/144] Retire Ubuntu 16.04 --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index de01ca0..9b20700 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -5,7 +5,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-16.04', 'ubuntu-18.04'] + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] cc: [ 'gcc', 'clang' ] name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} env: @@ -21,7 +21,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-16.04', 'ubuntu-18.04'] + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] name: Compile via Makefile on ${{ matrix.os }} steps: - uses: actions/checkout@v2 From a5b265dc38bf0b02c456cf0be7c319f782e73333 Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Sun, 20 Jun 2021 19:31:57 +0100 Subject: [PATCH 035/144] Also supply CXXFLAGS --- .github/workflows/actions.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 9b20700..08badd6 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -10,6 +10,7 @@ jobs: name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} env: CFLAGS: '-Wextra -Werror' + CXXFLAGS: '-Wextra -Werror' CC: ${{ matrix.cc }} CXX: ${{ matrix.cc }} steps: From d3f6f189c6b0ea7f5458224bb2bd3f94963e89c0 Mon Sep 17 00:00:00 2001 From: Dave Lambley <dave@lambley.me.uk> Date: Sun, 20 Jun 2021 19:43:26 +0100 Subject: [PATCH 036/144] Fix warning from clang /home/runner/work/beebasm/beebasm/src/symboltable.cpp:328:17: error: declaration shadows a variable in namespace 'std' [-Werror,-Wshadow] std::ostream & cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; ^ /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/iostream:61:18: note: previous declaration is here extern ostream cout; /// Linked to standard output ^ --- src/symboltable.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 92d8993..18a0f8a 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -325,9 +325,9 @@ void SymbolTable::RemoveSymbol( const std::string& symbol ) void SymbolTable::Dump(bool global, bool all, const char * labels_file) const { std::ofstream labels; - std::ostream & cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; + std::ostream & our_cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; - cout << "[{"; + our_cout << "[{"; bool bFirst = true; @@ -343,10 +343,10 @@ void SymbolTable::Dump(bool global, bool all, const char * labels_file) const { if ( !bFirst ) { - cout << ","; + our_cout << ","; } - cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + our_cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; bFirst = false; } @@ -359,16 +359,16 @@ void SymbolTable::Dump(bool global, bool all, const char * labels_file) const { if ( !bFirst ) { - cout << ","; + our_cout << ","; } - cout << "'" << it->m_identifier << "':" << it->m_addr << "L"; + our_cout << "'" << it->m_identifier << "':" << it->m_addr << "L"; bFirst = false; } } - cout << "}]" << endl; + our_cout << "}]" << endl; } void SymbolTable::PushBrace() From a78153a9f4a7596b01ab31f92aa7bd13723ed75d Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Mon, 21 Jun 2021 00:09:44 +0100 Subject: [PATCH 037/144] Update README.md with Dave Lambley's changes diff --git a/README.md b/README.md index 4ce8805..83a5f7b 100644 --- a/README.md +++ b/README.md @@ -724,6 +724,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line numbers from PUTBASIC in some cases. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) Added -writes, -dd and -labels options (thanks to tricky for these) + Added CMake support and man page (thanks to Dave Lambley) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ce8805..83a5f7b 100644 --- a/README.md +++ b/README.md @@ -724,6 +724,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Fixed incorrect line numbers from PUTBASIC in some cases. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) Added -writes, -dd and -labels options (thanks to tricky for these) + Added CMake support and man page (thanks to Dave Lambley) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From 3fdf8d8bc12e17e40599b80315468e92312b59c3 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 30 Jun 2021 20:09:55 +0100 Subject: [PATCH 038/144] Added string values. They can be concatenated. Quote quotes by doubling them up. TIME$ is no longer a special case for EQUS. Introduces a Value type that can be a string or a double. This uses its own string type. Alternatives were to not use a union, or to bring in a big dependency like boost to handle complex unions. --- src/VS2010/BeebAsm.vcxproj | 1 + src/VS2010/BeebAsm.vcxproj.filters | 3 + src/asmexception.h | 1 + src/commands.cpp | 138 +++----- src/expression.cpp | 511 ++++++++++++++++------------- src/lineparser.cpp | 6 +- src/lineparser.h | 18 +- src/symboltable.cpp | 21 +- src/symboltable.h | 13 +- src/value.h | 315 ++++++++++++++++++ 10 files changed, 688 insertions(+), 339 deletions(-) create mode 100644 src/value.h diff --git a/src/VS2010/BeebAsm.vcxproj b/src/VS2010/BeebAsm.vcxproj index 76a2292..10e8197 100644 --- a/src/VS2010/BeebAsm.vcxproj +++ b/src/VS2010/BeebAsm.vcxproj @@ -110,6 +110,7 @@ <ClInclude Include="..\sourcefile.h" /> <ClInclude Include="..\stringutils.h" /> <ClInclude Include="..\symboltable.h" /> + <ClInclude Include="..\value.h" /> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> diff --git a/src/VS2010/BeebAsm.vcxproj.filters b/src/VS2010/BeebAsm.vcxproj.filters index 29e4edb..cfd7bf3 100644 --- a/src/VS2010/BeebAsm.vcxproj.filters +++ b/src/VS2010/BeebAsm.vcxproj.filters @@ -107,5 +107,8 @@ <ClInclude Include="..\constants.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\value.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/asmexception.h b/src/asmexception.h index c7eadc8..5c06318 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -248,6 +248,7 @@ DEFINE_SYNTAX_EXCEPTION( OutOfRange, "Out of range." ); DEFINE_SYNTAX_EXCEPTION( BackwardsSkip, "Attempted to skip backwards to an address." ); DEFINE_SYNTAX_EXCEPTION( NoAnonSave, "Cannot specify SAVE without a filename if no default output filename has been specified." ); DEFINE_SYNTAX_EXCEPTION( OnlyOneAnonSave, "Can only use SAVE without a filename once per project." ); +DEFINE_SYNTAX_EXCEPTION( TypeMismatch, "Type mismatch." ); diff --git a/src/commands.cpp b/src/commands.cpp index ce630bb..ab0f6d4 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -203,9 +203,10 @@ void LineParser::HandleDefineLabel() } else { - // on the second pass, check that the label would be assigned the same value + // on the second pass, check that the label would be assigned the same numeric value - if ( SymbolTable::Instance().GetSymbol( fullSymbolName ) != ObjectCode::Instance().GetPC() ) + Value value = SymbolTable::Instance().GetSymbol( fullSymbolName ); + if ((value.GetType() != Value::NumberValue) || (value.GetNumber() != ObjectCode::Instance().GetPC() )) { throw AsmException_SyntaxError_SecondPassProblem( m_line, oldColumn ); } @@ -699,92 +700,35 @@ void LineParser::HandleEqub() throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); } - // handle TIME$ (special case of string) + Value value; - if ( m_column + 4 < m_line.length() && m_line.substr( m_column, 5 ) == "TIME$" ) + try { - m_column += 5; - std::string format = "%a,%d %b %Y.%H:%M:%S"; - if ( m_column < m_line.length() && m_line[ m_column ] == '(' ) - { - m_column++; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - if ( m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ) ; - } - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_column ); - } - format = m_line.substr( m_column + 1, endQuotePos - ( m_column + 1 ) ); - m_column = endQuotePos + 1; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - if ( m_line[ m_column ] != ')' ) - { - throw AsmException_SyntaxError_MismatchedParentheses( m_line, m_column ); - } - m_column++; - } - - char timeString[256]; - const time_t t = GlobalData::Instance().GetAssemblyTime(); - const struct tm* t_tm = localtime( &t ); - if ( strftime( timeString, sizeof( timeString ), format.c_str(), t_tm ) == 0 ) - { - throw AsmException_SyntaxError_TimeResultTooBig( m_line, m_column ); - } - HandleEqus( timeString ); + value = EvaluateExpression(); } - - // handle string - - else if ( m_column < m_line.length() && m_line[ m_column ] == '\"' ) + catch ( AsmException_SyntaxError_SymbolNotDefined& ) { - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) + if ( GlobalData::Instance().IsFirstPass() ) { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); + value = 0; } else { - string equs( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); - HandleEqus( equs ); + throw; } + } - m_column = endQuotePos + 1; + if (value.GetType() == Value::StringValue) + { + // handle equs + HandleEqus( value.GetString() ); } - else + else if (value.GetType() == Value::NumberValue) { // handle byte + int number = static_cast<int>(value.GetNumber()); - int value; - - try - { - value = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsFirstPass() ) - { - value = 0; - } - else - { - throw; - } - } - - if ( value > 0xFF ) + if ( number > 0xFF ) { throw AsmException_SyntaxError_NumberTooBig( m_line, m_column ); } @@ -793,13 +737,13 @@ void LineParser::HandleEqub() { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; - cout << setw(2) << ( value & 0xFF ); + cout << setw(2) << ( number & 0xFF ); cout << endl << nouppercase << dec << setfill( ' ' ); } try { - ObjectCode::Instance().PutByte( value & 0xFF ); + ObjectCode::Instance().PutByte( number & 0xFF ); } catch ( AsmException_AssembleError& e ) { @@ -808,6 +752,11 @@ void LineParser::HandleEqub() throw; } } + else + { + // Unknown value type; this should never happen. + assert(false); + } if ( !AdvanceAndCheckEndOfStatement() ) { @@ -828,10 +777,10 @@ void LineParser::HandleEqub() /*************************************************************************************************/ /** - LineParser::HandleEqus( const string& equs ) + LineParser::HandleEqus( const String& equs ) */ /*************************************************************************************************/ -void LineParser::HandleEqus( const string& equs ) +void LineParser::HandleEqus( const String& equs ) { if ( GlobalData::Instance().ShouldOutputAsm() ) { @@ -839,7 +788,7 @@ void LineParser::HandleEqus( const string& equs ) cout << setw(4) << ObjectCode::Instance().GetPC() << " "; } - for ( size_t i = 0; i < equs.length(); i++ ) + for ( size_t i = 0; i < equs.Length(); i++ ) { int mappedchar = ObjectCode::Instance().GetMapping( equs[ i ] ); @@ -1333,7 +1282,7 @@ void LineParser::HandleFor() // look for start value - double start = EvaluateExpression(); + double start = EvaluateExpressionAsDouble(); // look for comma @@ -1352,7 +1301,7 @@ void LineParser::HandleFor() // look for end value - double end = EvaluateExpression(); + double end = EvaluateExpressionAsDouble(); double step = 1.0; @@ -1367,7 +1316,7 @@ void LineParser::HandleFor() m_column++; - step = EvaluateExpression(); + step = EvaluateExpressionAsDouble(); if ( step == 0.0 ) { @@ -1566,9 +1515,9 @@ void LineParser::HandlePrint() } else { - // print in dec + // print number in decimal or string - double value; + Value value; try { @@ -1576,11 +1525,7 @@ void LineParser::HandlePrint() } catch ( AsmException_SyntaxError_SymbolNotDefined& ) { - if ( GlobalData::Instance().IsFirstPass() ) - { - value = 0.0; - } - else + if ( GlobalData::Instance().IsSecondPass() ) { throw; } @@ -1588,7 +1533,20 @@ void LineParser::HandlePrint() if ( GlobalData::Instance().IsSecondPass() ) { - cout << value << " "; + if (value.GetType() == Value::NumberValue) + { + cout << value.GetNumber() << " "; + } + else if (value.GetType() == Value::StringValue) + { + String text = value.GetString(); + const char* pstr = text.Text(); + for (int i = 0; i != text.Length(); ++i) + { + cout << *pstr; + ++pstr; + } + } } } } diff --git a/src/expression.cpp b/src/expression.cpp index bc5bd52..10e348b 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -100,7 +100,8 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "NOT(", 10, &LineParser::EvalNot }, { "LOG(", 10, &LineParser::EvalLog }, { "LN(", 10, &LineParser::EvalLn }, - { "EXP(", 10, &LineParser::EvalExp } + { "EXP(", 10, &LineParser::EvalExp }, + { "TIME$(", 10, &LineParser::EvalTime } }; @@ -112,15 +113,16 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = Parses a simple value. This may be - a decimal literal - a hex literal (prefixed by &) + - a string - a symbol (label) - a special value such as * (PC) @return double */ /*************************************************************************************************/ -double LineParser::GetValue() +Value LineParser::GetValue() { - double value = 0; + Value value; if ( m_column < m_line.length() && ( isdigit( m_line[ m_column ] ) || m_line[ m_column ] == '.' ) ) { @@ -128,12 +130,14 @@ double LineParser::GetValue() istringstream str( m_line ); str.seekg( m_column ); - str >> value; + double number; + str >> number; if (str.fail()) { // A decimal point with no number will cause this throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); } + value = number; m_column = static_cast< size_t >( str.tellg() ); } else if ( m_column < m_line.length() && ( m_line[ m_column ] == '&' || m_line[ m_column ] == '$' ) ) @@ -208,30 +212,79 @@ double LineParser::GetValue() value = static_cast< double >( m_line[ m_column + 1 ] ); m_column += 3; } + else if ( m_column < m_line.length() && m_line[ m_column ] == '\"' ) + { + // get string literal + + std::vector<char> text; + m_column++; + bool done = false; + while (!done && (m_column < m_line.length())) + { + char c = m_line[ m_column ]; + m_column++; + if (c == '\"') + { + if ((m_column < m_line.length()) && (m_line[m_column] == '\"')) + { + // Quote quoted by doubling + text.push_back(c); + m_column++; + } + else + { + done = true; + } + } + else + { + text.push_back(c); + } + } + if (!done) + { + throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); + } + value = String(text.data(), text.size()); + } else if ( m_column < m_line.length() && ( isalpha( m_line[ m_column ] ) || m_line[ m_column ] == '_' ) ) { // get a symbol int oldColumn = m_column; string symbolName = GetSymbolName(); - bool bFoundSymbol = false; - for ( int forLevel = m_sourceCode->GetForLevel(); forLevel >= 0; forLevel-- ) + if ((m_column < m_line.length()) && (m_line[m_column] == '$') && (symbolName == "TIME")) { - string fullSymbolName = symbolName + m_sourceCode->GetSymbolNameSuffix( forLevel ); + // Handle TIME$ with no parameters - if ( SymbolTable::Instance().IsSymbolDefined( fullSymbolName ) ) + m_column++; + + value = FormatAssemblyTime("%a,%d %b %Y.%H:%M:%S"); + } + else + { + // Regular symbol + + bool bFoundSymbol = false; + + for ( int forLevel = m_sourceCode->GetForLevel(); forLevel >= 0; forLevel-- ) { - value = SymbolTable::Instance().GetSymbol( fullSymbolName ); - bFoundSymbol = true; - break; + string fullSymbolName = symbolName + m_sourceCode->GetSymbolNameSuffix( forLevel ); + + if ( SymbolTable::Instance().IsSymbolDefined( fullSymbolName ) ) + { + value = SymbolTable::Instance().GetSymbol( fullSymbolName ); + bFoundSymbol = true; + break; + } } - } - if ( !bFoundSymbol ) - { - // symbol not known - throw AsmException_SyntaxError_SymbolNotDefined( m_line, oldColumn ); + if ( !bFoundSymbol ) + { + // symbol not known + throw AsmException_SyntaxError_SymbolNotDefined( m_line, oldColumn ); + } } } else @@ -252,7 +305,7 @@ double LineParser::GetValue() Evaluates an expression, and returns its value, also advancing the string pointer */ /*************************************************************************************************/ -double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) +Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) { // Reset stacks @@ -322,7 +375,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) throw AsmException_SyntaxError_ExpressionTooComplex( m_line, m_column ); } - double value; + Value value; try { @@ -522,6 +575,22 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) return m_valueStack[ 0 ]; } +/*************************************************************************************************/ +/** + LineParser::EvaluateExpressionAsDouble() + + Version of EvaluateExpression which returns its result as a double or throws a type mismatch +*/ +/*************************************************************************************************/ +double LineParser::EvaluateExpressionAsDouble( bool bAllowOneMismatchedCloseBracket ) +{ + Value value = EvaluateExpression( bAllowOneMismatchedCloseBracket ); + if (value.GetType() != Value::NumberValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return value.GetNumber(); +} /*************************************************************************************************/ @@ -533,7 +602,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) /*************************************************************************************************/ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< int >( EvaluateExpression( bAllowOneMismatchedCloseBracket ) ); + return static_cast< int >( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); } @@ -546,40 +615,121 @@ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) /*************************************************************************************************/ unsigned int LineParser::EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< unsigned int >( EvaluateExpression( bAllowOneMismatchedCloseBracket ) ); + return static_cast< unsigned int >( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); } - /*************************************************************************************************/ /** - LineParser::EvalAdd() + LineParser::StackTopTwoValues() + + Retrieve two values of matching type, or throw an exception */ /*************************************************************************************************/ -void LineParser::EvalAdd() +std::pair<Value, Value> LineParser::StackTopTwoValues() { if ( m_valueStackPtr < 2 ) { throw AsmException_SyntaxError_MissingValue( m_line, m_column ); } - m_valueStack[ m_valueStackPtr - 2 ] = m_valueStack[ m_valueStackPtr - 2 ] + m_valueStack[ m_valueStackPtr - 1 ]; - m_valueStackPtr--; + Value value1 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 1 ]; + if (value1.GetType() != value2.GetType()) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return std::pair<Value, Value>(value1, value2); } +/*************************************************************************************************/ +/** + LineParser::StackTopNumber() + + Retrieve a number from the top of the stack, or throw an exception +*/ +/*************************************************************************************************/ +double LineParser::StackTopNumber() +{ + if ( m_valueStackPtr < 1 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value = m_valueStack[ m_valueStackPtr - 1 ]; + if (value.GetType() != Value::NumberValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return value.GetNumber(); +} + /*************************************************************************************************/ /** - LineParser::EvalSubtract() + LineParser::StackTopTwoNumbers() + + Retrieve two values of numeric type, or throw an exception */ /*************************************************************************************************/ -void LineParser::EvalSubtract() +std::pair<double, double> LineParser::StackTopTwoNumbers() { if ( m_valueStackPtr < 2 ) { throw AsmException_SyntaxError_MissingValue( m_line, m_column ); } - m_valueStack[ m_valueStackPtr - 2 ] = m_valueStack[ m_valueStackPtr - 2 ] - m_valueStack[ m_valueStackPtr - 1 ]; + Value value1 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 1 ]; + if ((value1.GetType() != Value::NumberValue) || (value2.GetType() != Value::NumberValue)) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return std::pair<double, double>(value1.GetNumber(), value2.GetNumber()); +} + + +/*************************************************************************************************/ +/** + LineParser::StackTopTwoInts() + + Retrieve two values of numeric type and convert to ints, or throw an exception +*/ +/*************************************************************************************************/ +std::pair<int, int> LineParser::StackTopTwoInts() +{ + std::pair<double, double> pair = StackTopTwoNumbers(); + return std::pair<int, int>(static_cast<int>(pair.first), static_cast<int>(pair.second)); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalAdd() +*/ +/*************************************************************************************************/ +void LineParser::EvalAdd() +{ + std::pair<Value, Value> values = StackTopTwoValues(); + + if (values.first.GetType() == Value::NumberValue) + { + m_valueStack[ m_valueStackPtr - 2 ] = Value(values.first.GetNumber() + values.second.GetNumber()); + } + else if (values.first.GetType() == Value::StringValue) + { + m_valueStack[ m_valueStackPtr - 2 ] = Value(values.first.GetString() + values.second.GetString()); + } + m_valueStackPtr--; +} + +/*************************************************************************************************/ +/** + LineParser::EvalSubtract() +*/ +/*************************************************************************************************/ +void LineParser::EvalSubtract() +{ + std::pair<double, double> values = StackTopTwoNumbers(); + m_valueStack[ m_valueStackPtr - 2 ] = values.first - values.second; m_valueStackPtr--; } @@ -592,11 +742,8 @@ void LineParser::EvalSubtract() /*************************************************************************************************/ void LineParser::EvalMultiply() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = m_valueStack[ m_valueStackPtr - 2 ] * m_valueStack[ m_valueStackPtr - 1 ]; + std::pair<double, double> values = StackTopTwoNumbers(); + m_valueStack[ m_valueStackPtr - 2 ] = values.first * values.second; m_valueStackPtr--; } @@ -609,15 +756,12 @@ void LineParser::EvalMultiply() /*************************************************************************************************/ void LineParser::EvalDivide() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - if ( m_valueStack[ m_valueStackPtr - 1 ] == 0.0 ) + std::pair<double, double> values = StackTopTwoNumbers(); + if ( values.second == 0.0 ) { throw AsmException_SyntaxError_DivisionByZero( m_line, m_column - 1 ); } - m_valueStack[ m_valueStackPtr - 2 ] = m_valueStack[ m_valueStackPtr - 2 ] / m_valueStack[ m_valueStackPtr - 1 ]; + m_valueStack[ m_valueStackPtr - 2 ] = values.first / values.second; m_valueStackPtr--; } @@ -630,11 +774,8 @@ void LineParser::EvalDivide() /*************************************************************************************************/ void LineParser::EvalPower() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = pow( m_valueStack[ m_valueStackPtr - 2 ], m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<double, double> values = StackTopTwoNumbers(); + m_valueStack[ m_valueStackPtr - 2 ] = pow( values.first, values.second ); m_valueStackPtr--; if ( errno == ERANGE ) @@ -657,17 +798,13 @@ void LineParser::EvalPower() /*************************************************************************************************/ void LineParser::EvalDiv() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - if ( m_valueStack[ m_valueStackPtr - 1 ] == 0.0 ) + std::pair<int, int> values = StackTopTwoInts(); + + if ( values.second == 0 ) { throw AsmException_SyntaxError_DivisionByZero( m_line, m_column - 1 ); } - m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ) / - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >(values.first / values.second); m_valueStackPtr--; } @@ -680,17 +817,13 @@ void LineParser::EvalDiv() /*************************************************************************************************/ void LineParser::EvalMod() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - if ( m_valueStack[ m_valueStackPtr - 1 ] == 0.0 ) + std::pair<int, int> values = StackTopTwoInts(); + + if ( values.second == 0 ) { throw AsmException_SyntaxError_DivisionByZero( m_line, m_column - 1 ); } - m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ) % - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >(values.first % values.second); m_valueStackPtr--; } @@ -703,12 +836,10 @@ void LineParser::EvalMod() /*************************************************************************************************/ void LineParser::EvalShiftLeft() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - int val = static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ); - int shift = static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<int, int> values = StackTopTwoInts(); + + int val = values.first; + int shift = values.second; int result; if ( shift > 31 || shift < -31 ) @@ -741,12 +872,10 @@ void LineParser::EvalShiftLeft() /*************************************************************************************************/ void LineParser::EvalShiftRight() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - int val = static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ); - int shift = static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<int, int> values = StackTopTwoInts(); + + int val = values.first; + int shift = values.second; int result; if ( shift > 31 || shift < -31 ) @@ -779,13 +908,8 @@ void LineParser::EvalShiftRight() /*************************************************************************************************/ void LineParser::EvalAnd() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ) & - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + std::pair<int, int> values = StackTopTwoInts(); + m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >(values.first & values.second); m_valueStackPtr--; } @@ -798,13 +922,8 @@ void LineParser::EvalAnd() /*************************************************************************************************/ void LineParser::EvalOr() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ) | - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + std::pair<int, int> values = StackTopTwoInts(); + m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >(values.first | values.second); m_valueStackPtr--; } @@ -817,13 +936,8 @@ void LineParser::EvalOr() /*************************************************************************************************/ void LineParser::EvalEor() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ) ^ - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + std::pair<int, int> values = StackTopTwoInts(); + m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >(values.first ^ values.second); m_valueStackPtr--; } @@ -836,11 +950,8 @@ void LineParser::EvalEor() /*************************************************************************************************/ void LineParser::EvalEqual() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] == m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) == 0) ? -1 : 0; m_valueStackPtr--; } @@ -853,11 +964,8 @@ void LineParser::EvalEqual() /*************************************************************************************************/ void LineParser::EvalNotEqual() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] != m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) != 0) ? -1 : 0; m_valueStackPtr--; } @@ -870,11 +978,8 @@ void LineParser::EvalNotEqual() /*************************************************************************************************/ void LineParser::EvalLessThanOrEqual() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] <= m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) <= 0) ? -1 : 0; m_valueStackPtr--; } @@ -887,11 +992,8 @@ void LineParser::EvalLessThanOrEqual() /*************************************************************************************************/ void LineParser::EvalMoreThanOrEqual() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] >= m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) >= 0) ? -1 : 0; m_valueStackPtr--; } @@ -904,11 +1006,8 @@ void LineParser::EvalMoreThanOrEqual() /*************************************************************************************************/ void LineParser::EvalLessThan() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] < m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) < 0) ? -1 : 0; m_valueStackPtr--; } @@ -921,11 +1020,8 @@ void LineParser::EvalLessThan() /*************************************************************************************************/ void LineParser::EvalMoreThan() { - if ( m_valueStackPtr < 2 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 2 ] = -( m_valueStack[ m_valueStackPtr - 2 ] > m_valueStack[ m_valueStackPtr - 1 ] ); + std::pair<Value, Value> values = StackTopTwoValues(); + m_valueStack[ m_valueStackPtr - 2 ] = (Value::Compare(values.first, values.second) > 0) ? -1 : 0; m_valueStackPtr--; } @@ -938,11 +1034,7 @@ void LineParser::EvalMoreThan() /*************************************************************************************************/ void LineParser::EvalNegate() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = -m_valueStack[ m_valueStackPtr - 1 ]; + m_valueStack[ m_valueStackPtr - 1 ] = -StackTopNumber(); } @@ -954,12 +1046,8 @@ void LineParser::EvalNegate() /*************************************************************************************************/ void LineParser::EvalNot() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( - ~static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + int value = ~static_cast<int>(StackTopNumber()); + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -987,12 +1075,8 @@ void LineParser::EvalPosate() /*************************************************************************************************/ void LineParser::EvalLo() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) & 0xFF ); + int value = static_cast<int>(StackTopNumber()) & 0xFF; + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1004,12 +1088,8 @@ void LineParser::EvalLo() /*************************************************************************************************/ void LineParser::EvalHi() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( - ( static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) & 0xffff ) >> 8 ); + int value = (static_cast<int>(StackTopNumber()) & 0xffff) >> 8; + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1021,11 +1101,7 @@ void LineParser::EvalHi() /*************************************************************************************************/ void LineParser::EvalSin() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = sin( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = sin( StackTopNumber() ); } @@ -1037,11 +1113,7 @@ void LineParser::EvalSin() /*************************************************************************************************/ void LineParser::EvalCos() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = cos( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = cos( StackTopNumber() ); } @@ -1053,11 +1125,7 @@ void LineParser::EvalCos() /*************************************************************************************************/ void LineParser::EvalTan() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = tan( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = tan( StackTopNumber() ); } @@ -1069,11 +1137,7 @@ void LineParser::EvalTan() /*************************************************************************************************/ void LineParser::EvalArcSin() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = asin( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = asin( StackTopNumber() ); if ( errno == EDOM ) { @@ -1090,11 +1154,7 @@ void LineParser::EvalArcSin() /*************************************************************************************************/ void LineParser::EvalArcCos() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = acos( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = acos( StackTopNumber() ); if ( errno == EDOM ) { @@ -1111,11 +1171,7 @@ void LineParser::EvalArcCos() /*************************************************************************************************/ void LineParser::EvalArcTan() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = atan( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = atan( StackTopNumber() ); if ( errno == EDOM ) { @@ -1132,11 +1188,7 @@ void LineParser::EvalArcTan() /*************************************************************************************************/ void LineParser::EvalLog() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = log10( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = log10( StackTopNumber() ); if ( errno == EDOM || errno == ERANGE ) { @@ -1153,11 +1205,7 @@ void LineParser::EvalLog() /*************************************************************************************************/ void LineParser::EvalLn() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = log( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = log( StackTopNumber() ); if ( errno == EDOM || errno == ERANGE ) { @@ -1174,11 +1222,7 @@ void LineParser::EvalLn() /*************************************************************************************************/ void LineParser::EvalExp() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = exp( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = exp( StackTopNumber() ); if ( errno == ERANGE ) { @@ -1195,11 +1239,7 @@ void LineParser::EvalExp() /*************************************************************************************************/ void LineParser::EvalSqrt() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = sqrt( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = sqrt( StackTopNumber() ); if ( errno == EDOM ) { @@ -1216,11 +1256,7 @@ void LineParser::EvalSqrt() /*************************************************************************************************/ void LineParser::EvalDegToRad() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = m_valueStack[ m_valueStackPtr - 1 ] * const_pi / 180.0; + m_valueStack[ m_valueStackPtr - 1 ] = StackTopNumber() * const_pi / 180.0; } @@ -1232,11 +1268,7 @@ void LineParser::EvalDegToRad() /*************************************************************************************************/ void LineParser::EvalRadToDeg() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = m_valueStack[ m_valueStackPtr - 1 ] * 180.0 / const_pi; + m_valueStack[ m_valueStackPtr - 1 ] = StackTopNumber() * 180.0 / const_pi; } @@ -1248,12 +1280,8 @@ void LineParser::EvalRadToDeg() /*************************************************************************************************/ void LineParser::EvalInt() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( - static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ) ); + static_cast< int >( StackTopNumber() ) ); } @@ -1265,11 +1293,7 @@ void LineParser::EvalInt() /*************************************************************************************************/ void LineParser::EvalAbs() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = abs( m_valueStack[ m_valueStackPtr - 1 ] ); + m_valueStack[ m_valueStackPtr - 1 ] = abs( StackTopNumber() ); } @@ -1281,12 +1305,7 @@ void LineParser::EvalAbs() /*************************************************************************************************/ void LineParser::EvalSgn() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - - double val = m_valueStack[ m_valueStackPtr - 1 ]; + double val = StackTopNumber(); m_valueStack[ m_valueStackPtr - 1 ] = ( val < 0.0 ) ? -1.0 : ( ( val > 0.0 ) ? 1.0 : 0.0 ); } @@ -1299,12 +1318,7 @@ void LineParser::EvalSgn() /*************************************************************************************************/ void LineParser::EvalRnd() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - - double val = m_valueStack[ m_valueStackPtr - 1 ]; + double val = StackTopNumber(); double result = 0.0; if ( val < 1.0f ) @@ -1322,3 +1336,44 @@ void LineParser::EvalRnd() m_valueStack[ m_valueStackPtr - 1 ] = result; } + + +/*************************************************************************************************/ +/** + LineParser::EvalTime() +*/ +/*************************************************************************************************/ +void LineParser::EvalTime() +{ + if ( m_valueStackPtr < 1 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value = m_valueStack[ m_valueStackPtr - 1 ]; + if (value.GetType() != Value::StringValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + m_valueStack[ m_valueStackPtr - 1 ] = FormatAssemblyTime(value.GetString().Text()); +} + + +/*************************************************************************************************/ +/** + LineParser::FormatAssemblyTime() + + Format the assembly time using the given strftime format string +*/ +/*************************************************************************************************/ +Value LineParser::FormatAssemblyTime(const char* formatString) +{ + char timeString[256]; + const time_t t = GlobalData::Instance().GetAssemblyTime(); + const struct tm* t_tm = localtime( &t ); + int length = strftime( timeString, sizeof( timeString ), formatString, t_tm ); + if ( length == 0 ) + { + throw AsmException_SyntaxError_TimeResultTooBig( m_line, m_column ); + } + return String(timeString, length); +} diff --git a/src/lineparser.cpp b/src/lineparser.cpp index cf38147..c4499d6 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -176,7 +176,7 @@ void LineParser::Process() m_column++; } - double value = EvaluateExpression(); + Value value = EvaluateExpression(); if ( GlobalData::Instance().IsFirstPass() ) { @@ -227,7 +227,7 @@ void LineParser::Process() { if ( !SymbolTable::Instance().IsSymbolDefined( paramName ) ) { - double value = EvaluateExpression(); + Value value = EvaluateExpression(); SymbolTable::Instance().AddSymbol( paramName, value ); } else if ( GlobalData::Instance().IsSecondPass() ) @@ -238,7 +238,7 @@ void LineParser::Process() // macro parameter rather than the new value of the outer macro // parameter. See local-forward-branch-5.6502 for an example. SymbolTable::Instance().RemoveSymbol( paramName ); - double value = EvaluateExpression(); + Value value = EvaluateExpression(); SymbolTable::Instance().AddSymbol( paramName, value ); } } diff --git a/src/lineparser.h b/src/lineparser.h index a793e34..110f206 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -24,6 +24,7 @@ #define LINEPARSER_H_ #include <string> +#include "value.h" class SourceCode; @@ -135,7 +136,7 @@ class LineParser void HandleInclude(); void HandleIncBin(); void HandleEqub(); - void HandleEqus(const std::string& equs); + void HandleEqus(const String& equs); void HandleEquw(); void HandleEqud(); void HandleAssert(); @@ -163,10 +164,17 @@ class LineParser // expression evaluating methods - double EvaluateExpression( bool bAllowOneMismatchedCloseBracket = false ); + Value EvaluateExpression( bool bAllowOneMismatchedCloseBracket = false ); + double EvaluateExpressionAsDouble( bool bAllowOneMismatchedCloseBracket = false ); int EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket = false ); unsigned int EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatchedCloseBracket = false ); - double GetValue(); + Value GetValue(); + + // convenience functions for getting operator parameters from the stack + std::pair<Value, Value> StackTopTwoValues(); + double LineParser::StackTopNumber(); + std::pair<double, double> StackTopTwoNumbers(); + std::pair<int, int> StackTopTwoInts(); void EvalAdd(); void EvalSubtract(); @@ -208,7 +216,9 @@ class LineParser void EvalLog(); void EvalLn(); void EvalExp(); + void EvalTime(); + Value FormatAssemblyTime(const char* formatString); SourceCode* m_sourceCode; std::string m_line; @@ -222,7 +232,7 @@ class LineParser #define MAX_VALUES 128 #define MAX_OPERATORS 32 - double m_valueStack[ MAX_VALUES ]; + Value m_valueStack[ MAX_VALUES ]; Operator m_operatorStack[ MAX_OPERATORS ]; int m_valueStackPtr; int m_operatorStackPtr; diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 18a0f8a..54f5d68 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -128,10 +128,10 @@ bool SymbolTable::IsSymbolDefined( const std::string& symbol ) const Adds a symbol to the symbol table with the supplied value @param symbol The symbol to add - @param int Its value + @param value Its value */ /*************************************************************************************************/ -void SymbolTable::AddSymbol( const std::string& symbol, double value, bool isLabel ) +void SymbolTable::AddSymbol( const std::string& symbol, Value value, bool isLabel ) { assert( !IsSymbolDefined( symbol ) ); m_map.insert( make_pair( symbol, Symbol( value, isLabel ) ) ); @@ -272,7 +272,7 @@ bool SymbolTable::AddCommandLineSymbol( const std::string& expr ) @param symbol The name of the symbol to look for */ /*************************************************************************************************/ -double SymbolTable::GetSymbol( const std::string& symbol ) const +Value SymbolTable::GetSymbol( const std::string& symbol ) const { assert( IsSymbolDefined( symbol ) ); return m_map.find( symbol )->second.GetValue(); @@ -341,14 +341,19 @@ void SymbolTable::Dump(bool global, bool all, const char * labels_file) const if ( symbol.IsLabel() && symbolName.find_first_of( '@' ) == string::npos ) { - if ( !bFirst ) + // This doesn't output string valued symbols + Value value = symbol.GetValue(); + if (value.GetType() == Value::NumberValue) { - our_cout << ","; - } + if ( !bFirst ) + { + our_cout << ","; + } - our_cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + our_cout << "'" << symbolName << "':" << value.GetNumber() << "L"; - bFirst = false; + bFirst = false; + } } } } diff --git a/src/symboltable.h b/src/symboltable.h index 8fb66aa..e7c4ba2 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -29,6 +29,7 @@ #include <string> #include <vector> +#include "value.h" class SymbolTable { @@ -38,10 +39,10 @@ class SymbolTable static void Destroy(); static inline SymbolTable& Instance() { assert( m_gInstance != NULL ); return *m_gInstance; } - void AddSymbol( const std::string& symbol, double value, bool isLabel = false ); + void AddSymbol( const std::string& symbol, Value value, bool isLabel = false ); bool AddCommandLineSymbol( const std::string& expr ); void ChangeSymbol( const std::string& symbol, double value ); - double GetSymbol( const std::string& symbol ) const; + Value GetSymbol( const std::string& symbol ) const; bool IsSymbolDefined( const std::string& symbol ) const; void RemoveSymbol( const std::string& symbol ); @@ -58,15 +59,15 @@ class SymbolTable { public: - Symbol( double value, bool isLabel ) : m_value( value ), m_isLabel( isLabel ) {} + Symbol( Value value, bool isLabel ) : m_value( value ), m_isLabel( isLabel ) {} - void SetValue( double d ) { m_value = d; } - double GetValue() const { return m_value; } + void SetValue( Value value ) { m_value = value; } + Value GetValue() const { return m_value; } bool IsLabel() const { return m_isLabel; } private: - double m_value; + Value m_value; bool m_isLabel; }; diff --git a/src/value.h b/src/value.h new file mode 100644 index 0000000..d640777 --- /dev/null +++ b/src/value.h @@ -0,0 +1,315 @@ +/*************************************************************************************************/ +/** + value.h + + + Copyright (C) Charles Reilly 2021 + + This file is part of BeebAsm. + + BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU + General Public License as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with BeebAsm, as + COPYING.txt. If not, see <http://www.gnu.org/licenses/>. +*/ +/*************************************************************************************************/ + +#ifndef VALUE_H_ +#define VALUE_H_ + +#include <cassert> + +// A simple immutable string buffer with a length and a reference count. +// This doesn't have constructors, etc. so it can be stuffed into a union. +struct StringHeader +{ +private: + unsigned int m_refCount; + unsigned int m_length; + +public: + static StringHeader* Allocate(const char* text, unsigned int length) + { + StringHeader* header = Allocate(length); + char* buffer = StringBuffer(header); + memcpy(buffer, text, length); + buffer[length] = 0; + return header; + } + + static const char* StringData(StringHeader* header) + { + return StringBuffer(header); + } + + static void AddRef(StringHeader* header) + { + header->m_refCount++; + } + + static void Release(StringHeader** ppheader) + { + StringHeader*& header = *ppheader; + header->m_refCount--; + if (header->m_refCount == 0) + { + free(header); + } + header = 0; + } + + static unsigned int Length(StringHeader* header) + { + return header->m_length; + } + + static int Compare(StringHeader* header1, StringHeader* header2) + { + int result = memcmp(StringData(header1), StringData(header2), std::min(header1->m_length, header2->m_length)); + if (result != 0) + return result; + return Compare(header1->m_length, header2->m_length); + } + + static StringHeader* Concat(StringHeader* header1, StringHeader* header2) + { + int length = header1->m_length + header2->m_length; + StringHeader* header = Allocate(length); + char* buffer = StringBuffer(header); + memcpy(buffer, StringData(header1), header1->m_length); + memcpy(buffer + header1->m_length, StringData(header2), header2->m_length); + buffer[length] = 0; + return header; + } +private: + static char* StringBuffer(StringHeader* header) + { + return reinterpret_cast<char*>(header) + sizeof(StringHeader); + } + + static StringHeader* Allocate(unsigned int length) + { + int fullLength = sizeof(StringHeader) + length + 1; + StringHeader* header = static_cast<StringHeader*>(malloc(fullLength)); + if (!header) + return 0; + header->m_refCount = 0; + header->m_length = length; + return header; + } + + static int Compare(unsigned int a, unsigned int b) + { + if (a == b) + return 0; + else if (a < b) + return -1; + else + return 1; + } +}; + +// A simple immutable string. +class String +{ +public: + ~String() + { + StringHeader::Release(&m_header); + } + String(const String& that) + { + m_header = that.m_header; + StringHeader::AddRef(m_header); + } + String(const char* text, unsigned int length) + { + m_header = StringHeader::Allocate(text, length); + StringHeader::AddRef(m_header); + } + String& operator=(const String& that) + { + if (m_header != that.m_header) + { + StringHeader::Release(&m_header); + m_header = that.m_header; + StringHeader::AddRef(m_header); + } + return *this; + } + String operator+(const String& that) + { + StringHeader* header = StringHeader::Concat(m_header, that.m_header); + return String(header); + } + unsigned int Length() const + { + return StringHeader::Length(m_header); + } + const char* Text() const + { + return StringHeader::StringData(m_header); + } + char operator[](int index) const + { + assert((0 <= index) && (static_cast<unsigned int>(index) < Length())); + return Text()[index]; + } +private: + friend class Value; + StringHeader* m_header; + + String(StringHeader* header) + { + m_header = header; + StringHeader::AddRef(m_header); + } +}; + +// A value that can be a string or a number +class Value +{ +public: + enum Type + { + NumberValue, + StringValue + }; + + Value() + { + m_type = NumberValue; + m_number = 0; + } + + Value(double number) + { + m_type = NumberValue; + m_number = number; + } + + Value(String s) + { + m_type = StringValue; + m_string = s.m_header; + StringHeader::AddRef(m_string); + } + + Value(const Value& that) + { + Assign(that); + } + + Value& operator=(const Value& that) + { + if ((m_type != StringValue) || (that.m_type != StringValue) || (m_string != that.m_string)) + { + Clear(); + Assign(that); + } + return *this; + } + + ~Value() + { + Clear(); + } + + Type GetType() const + { + return m_type; + } + + double GetNumber() const + { + assert(m_type == NumberValue); + return m_number; + } + + String GetString() const + { + assert(m_type == StringValue); + return String(m_string); + } + + static int Compare(Value value1, Value value2) + { + if (value1.GetType() == value2.GetType()) + { + if (value1.GetType() == NumberValue) + { + return Compare(value1.GetNumber(), value2.GetNumber()); + } + else if (value1.GetType() == StringValue) + { + return StringHeader::Compare(value1.m_string, value2.m_string); + } + else + { + assert(false); + } + return 0; + } + if (value1.GetType() < value2.GetType()) + { + return -1; + } + else + { + return 1; + } + } + +private: + union + { + double m_number; + StringHeader* m_string; + }; + + Type m_type; + + void Clear() + { + if ((m_type == StringValue) && m_string) + { + StringHeader::Release(&m_string); + } + } + + void Assign(const Value& that) + { + m_type = that.m_type; + if (m_type == NumberValue) + { + m_number = that.m_number; + } + else if (m_type == StringValue) + { + m_string = that.m_string; + StringHeader::AddRef(m_string); + } + else + { + assert(false); + } + } + + static int Compare(double a, double b) + { + if (a == b) + return 0; + else if (a < b) + return -1; + else + return 1; + } +}; + +#endif // VALUE_H_ From c6ee0f4943407ab58e9d14d847600bba658aee4f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 30 Jun 2021 22:20:40 +0100 Subject: [PATCH 039/144] VAL (string to number) and STR$ (number to string) --- src/expression.cpp | 67 ++++++++++++++++++++++++++++++++++++++-------- src/lineparser.h | 3 +++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 10e348b..bdb147e 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -101,7 +101,9 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "LOG(", 10, &LineParser::EvalLog }, { "LN(", 10, &LineParser::EvalLn }, { "EXP(", 10, &LineParser::EvalExp }, - { "TIME$(", 10, &LineParser::EvalTime } + { "TIME$(", 10, &LineParser::EvalTime }, + { "STR$(", 10, &LineParser::EvalStr }, + { "VAL(", 10, &LineParser::EvalVal } }; @@ -642,6 +644,28 @@ std::pair<Value, Value> LineParser::StackTopTwoValues() } +/*************************************************************************************************/ +/** + LineParser::StackTopString() + + Retrieve a string from the top of the stack, or throw an exception +*/ +/*************************************************************************************************/ +String LineParser::StackTopString() +{ + if ( m_valueStackPtr < 1 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value = m_valueStack[ m_valueStackPtr - 1 ]; + if (value.GetType() != Value::StringValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return value.GetString(); +} + + /*************************************************************************************************/ /** LineParser::StackTopNumber() @@ -1345,16 +1369,7 @@ void LineParser::EvalRnd() /*************************************************************************************************/ void LineParser::EvalTime() { - if ( m_valueStackPtr < 1 ) - { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); - } - Value value = m_valueStack[ m_valueStackPtr - 1 ]; - if (value.GetType() != Value::StringValue) - { - throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); - } - m_valueStack[ m_valueStackPtr - 1 ] = FormatAssemblyTime(value.GetString().Text()); + m_valueStack[ m_valueStackPtr - 1 ] = FormatAssemblyTime(StackTopString().Text()); } @@ -1377,3 +1392,33 @@ Value LineParser::FormatAssemblyTime(const char* formatString) } return String(timeString, length); } + + +/*************************************************************************************************/ +/** + LineParser::EvalStr() +*/ +/*************************************************************************************************/ +void LineParser::EvalStr() +{ + ostringstream stream; + stream << StackTopNumber(); + string result = stream.str(); + + m_valueStack[ m_valueStackPtr - 1 ] = String(result.data(), result.length()); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalVal() +*/ +/*************************************************************************************************/ +void LineParser::EvalVal() +{ + String str = StackTopString(); + char* end; + double value = strtod(str.Text(), &end); + + m_valueStack[ m_valueStackPtr - 1 ] = value; +} diff --git a/src/lineparser.h b/src/lineparser.h index 110f206..d568f4c 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -172,6 +172,7 @@ class LineParser // convenience functions for getting operator parameters from the stack std::pair<Value, Value> StackTopTwoValues(); + String LineParser::StackTopString(); double LineParser::StackTopNumber(); std::pair<double, double> StackTopTwoNumbers(); std::pair<int, int> StackTopTwoInts(); @@ -217,6 +218,8 @@ class LineParser void EvalLn(); void EvalExp(); void EvalTime(); + void EvalStr(); + void EvalVal(); Value FormatAssemblyTime(const char* formatString); From 2f915e692c2c4001e4eba7941a2c49491f83265a Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 00:21:15 +0100 Subject: [PATCH 040/144] LEN, CHR$, ASC, MID$, STRING$ This is moderately complicated because it needed support for functions with more than one parameter. --- README.md | 29 ++--- src/asmexception.h | 1 + src/expression.cpp | 275 +++++++++++++++++++++++++++++++++++---------- src/lineparser.cpp | 13 ++- src/lineparser.h | 8 +- src/value.h | 42 +++++++ 6 files changed, 293 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 83a5f7b..26c98af 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,23 @@ NOT(val) Return the bitwise 1's complement of val LOG(val) Return the base 10 log of val LN(val) Return the natural log of val EXP(val) Return e raised to the power of val +VAL(str) Return the value of a decimal number in a string +STR$(val) Return the number val converted to a string +LEN(str) Return the length of str +CHR$(val) Return a string with a single character with ASCII value val +ASC(str) Return the ASCII value of the first character of str +MID$(str,index,length) + Return length characters of str starting at (one-based) index +STRINGS$(count,str) + Return str repeated count times +TIME$ Return assembly date/time in format "Day,DD Mon Year.HH:MM:SS" +TIME$("fmt") Return assembly date/time in a format determined by "fmt", which + is the same format used by the C library strftime() ``` +The assembly date/time is constant throughout the assembly; every use of `TIME$` +will return the same date/time. + Also, some constants are defined: ``` @@ -248,19 +263,7 @@ TRUE Returns -1 CPU The value set by the CPU assembler directive (see below) ``` -Within `EQUB/EQUS` only you can also use the expressions: - -``` -TIME$ Return assembly date/time in format "Day,DD Mon Year.HH:MM:SS" - -TIME$("fmt") Return assembly date/time in a format determined by "fmt", which - is the same format used by the C library strftime(). -``` - -The assembly date/time is constant throughout the assembly; every use of `TIME$` -will return the same date/time. - -Variables can be defined at any point using the BASIC syntax, i.e. `addr = &70`. +Variables can be defined at any point using the BASIC syntax, i.e. `addr = &70` or `name = "Bob"`. Quotes in strings are quoted by doubling, e.g. `"a""b"` for the string `a"b`. Note that it is not possible to reassign variables once defined. However `FOR...NEXT` blocks have their own scope (more on this later). diff --git a/src/asmexception.h b/src/asmexception.h index 5c06318..6af5d10 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -201,6 +201,7 @@ DEFINE_SYNTAX_EXCEPTION( MissingQuote, "Unterminated string." ); DEFINE_SYNTAX_EXCEPTION( MissingComma, "Missing comma." ); DEFINE_SYNTAX_EXCEPTION( IllegalOperation, "Operation attempted with invalid or out of range values." ); DEFINE_SYNTAX_EXCEPTION( TimeResultTooBig, "TIME$() result too big." ); +DEFINE_SYNTAX_EXCEPTION( ParameterCount, "Wrong number of parameters." ); // assembler parsing exceptions DEFINE_SYNTAX_EXCEPTION( NoImplied, "Implied mode not allowed for this instruction." ); diff --git a/src/expression.cpp b/src/expression.cpp index bdb147e..6fe4928 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -45,65 +45,71 @@ using namespace std; const LineParser::Operator LineParser::m_gaBinaryOperatorTable[] = { - { ")", -1, NULL }, // special case - { "]", -1, NULL }, // special case - - { "^", 7, &LineParser::EvalPower }, - { "*", 6, &LineParser::EvalMultiply }, - { "/", 6, &LineParser::EvalDivide }, - { "%", 6, &LineParser::EvalMod }, - { "DIV", 6, &LineParser::EvalDiv }, - { "MOD", 6, &LineParser::EvalMod }, - { "<<", 6, &LineParser::EvalShiftLeft }, - { ">>", 6, &LineParser::EvalShiftRight }, - { "+", 5, &LineParser::EvalAdd }, - { "-", 5, &LineParser::EvalSubtract }, - { "==", 4, &LineParser::EvalEqual }, - { "=", 4, &LineParser::EvalEqual }, - { "<>", 4, &LineParser::EvalNotEqual }, - { "!=", 4, &LineParser::EvalNotEqual }, - { "<=", 4, &LineParser::EvalLessThanOrEqual }, - { ">=", 4, &LineParser::EvalMoreThanOrEqual }, - { "<", 4, &LineParser::EvalLessThan }, - { ">", 4, &LineParser::EvalMoreThan }, - { "AND", 3, &LineParser::EvalAnd }, - { "OR", 2, &LineParser::EvalOr }, - { "EOR", 2, &LineParser::EvalEor } + { ")", -1, 0, NULL }, // special case + { "]", -1, 0, NULL }, // special case + { ",", -1, 0, NULL }, // special case + + { "^", 7, 0, &LineParser::EvalPower }, + { "*", 6, 0, &LineParser::EvalMultiply }, + { "/", 6, 0, &LineParser::EvalDivide }, + { "%", 6, 0, &LineParser::EvalMod }, + { "DIV", 6, 0, &LineParser::EvalDiv }, + { "MOD", 6, 0, &LineParser::EvalMod }, + { "<<", 6, 0, &LineParser::EvalShiftLeft }, + { ">>", 6, 0, &LineParser::EvalShiftRight }, + { "+", 5, 0, &LineParser::EvalAdd }, + { "-", 5, 0, &LineParser::EvalSubtract }, + { "==", 4, 0, &LineParser::EvalEqual }, + { "=", 4, 0, &LineParser::EvalEqual }, + { "<>", 4, 0, &LineParser::EvalNotEqual }, + { "!=", 4, 0, &LineParser::EvalNotEqual }, + { "<=", 4, 0, &LineParser::EvalLessThanOrEqual }, + { ">=", 4, 0, &LineParser::EvalMoreThanOrEqual }, + { "<", 4, 0, &LineParser::EvalLessThan }, + { ">", 4, 0, &LineParser::EvalMoreThan }, + { "AND", 3, 0, &LineParser::EvalAnd }, + { "OR", 2, 0, &LineParser::EvalOr }, + { "EOR", 2, 0, &LineParser::EvalEor } }; const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { - { "(", -1, NULL }, // special case - { "[", -1, NULL }, // special case - - { "-", 8, &LineParser::EvalNegate }, - { "+", 8, &LineParser::EvalPosate }, - { "HI(", 10, &LineParser::EvalHi }, - { "LO(", 10, &LineParser::EvalLo }, - { ">", 10, &LineParser::EvalHi }, - { "<", 10, &LineParser::EvalLo }, - { "SIN(", 10, &LineParser::EvalSin }, - { "COS(", 10, &LineParser::EvalCos }, - { "TAN(", 10, &LineParser::EvalTan }, - { "ASN(", 10, &LineParser::EvalArcSin }, - { "ACS(", 10, &LineParser::EvalArcCos }, - { "ATN(", 10, &LineParser::EvalArcTan }, - { "SQR(", 10, &LineParser::EvalSqrt }, - { "RAD(", 10, &LineParser::EvalDegToRad }, - { "DEG(", 10, &LineParser::EvalRadToDeg }, - { "INT(", 10, &LineParser::EvalInt }, - { "ABS(", 10, &LineParser::EvalAbs }, - { "SGN(", 10, &LineParser::EvalSgn }, - { "RND(", 10, &LineParser::EvalRnd }, - { "NOT(", 10, &LineParser::EvalNot }, - { "LOG(", 10, &LineParser::EvalLog }, - { "LN(", 10, &LineParser::EvalLn }, - { "EXP(", 10, &LineParser::EvalExp }, - { "TIME$(", 10, &LineParser::EvalTime }, - { "STR$(", 10, &LineParser::EvalStr }, - { "VAL(", 10, &LineParser::EvalVal } + { "(", -1, 0, NULL }, // special case + { "[", -1, 0, NULL }, // special case + + { "-", 8, 0, &LineParser::EvalNegate }, + { "+", 8, 0, &LineParser::EvalPosate }, + { "HI(", 10, 1, &LineParser::EvalHi }, + { "LO(", 10, 1, &LineParser::EvalLo }, + { ">", 10, 0, &LineParser::EvalHi }, + { "<", 10, 0, &LineParser::EvalLo }, + { "SIN(", 10, 1, &LineParser::EvalSin }, + { "COS(", 10, 1, &LineParser::EvalCos }, + { "TAN(", 10, 1, &LineParser::EvalTan }, + { "ASN(", 10, 1, &LineParser::EvalArcSin }, + { "ACS(", 10, 1, &LineParser::EvalArcCos }, + { "ATN(", 10, 1, &LineParser::EvalArcTan }, + { "SQR(", 10, 1, &LineParser::EvalSqrt }, + { "RAD(", 10, 1, &LineParser::EvalDegToRad }, + { "DEG(", 10, 1, &LineParser::EvalRadToDeg }, + { "INT(", 10, 1, &LineParser::EvalInt }, + { "ABS(", 10, 1, &LineParser::EvalAbs }, + { "SGN(", 10, 1, &LineParser::EvalSgn }, + { "RND(", 10, 1, &LineParser::EvalRnd }, + { "NOT(", 10, 1, &LineParser::EvalNot }, + { "LOG(", 10, 1, &LineParser::EvalLog }, + { "LN(", 10, 1, &LineParser::EvalLn }, + { "EXP(", 10, 1, &LineParser::EvalExp }, + { "TIME$(", 10, 1, &LineParser::EvalTime }, + { "STR$(", 10, 1, &LineParser::EvalStr }, + { "VAL(", 10, 1, &LineParser::EvalVal }, + { "LEN(", 10, 1, &LineParser::EvalLen }, + { "CHR$(", 10, 1, &LineParser::EvalChr }, + { "ASC(", 10, 1, &LineParser::EvalAsc }, + { "MID$(", 10, 3, &LineParser::EvalMid }, + { "STRING$(", 10, 2, &LineParser::EvalString } }; @@ -318,11 +324,15 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) int bracketCount = 0; + // When we know a '(' is coming (because it was the end of a token) this is the number of commas to expect + // in the parameter list, i.e. one less than the number of parameters. + int pendingCommaCount = 0; + TYPE expected = VALUE_OR_UNARY; // Iterate through the expression - while ( AdvanceAndCheckEndOfSubStatement() ) + while ( AdvanceAndCheckEndOfSubStatement(bracketCount == 0) ) { if ( expected == VALUE_OR_UNARY ) { @@ -360,6 +370,7 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) if ( len > 1 && token[ len - 1 ] == '(' ) { + pendingCommaCount = m_gaUnaryOperatorTable[ matchedToken ].parameterCount - 1; m_column--; assert( m_line[ m_column ] == '(' ); } @@ -406,7 +417,7 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) { // If unary operator *was* found... - const Operator& thisOp = m_gaUnaryOperatorTable[ matchedToken ]; + Operator thisOp = m_gaUnaryOperatorTable[ matchedToken ]; if ( thisOp.handler != NULL ) { @@ -425,6 +436,9 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) } else { + // The open bracket's parameterCount counts down the number of commas expected. + thisOp.parameterCount = pendingCommaCount; + pendingCommaCount = 0; bracketCount++; } @@ -504,9 +518,14 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) } else { - // is a close bracket + // is a close bracket or parameter separator + + bool separator = strcmp(thisOp.token, ",") == 0; - bracketCount--; + if (!separator) + { + bracketCount--; + } bool bFoundMatchingBracket = false; @@ -526,8 +545,39 @@ Value LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) } } - if ( !bFoundMatchingBracket ) + if ( bFoundMatchingBracket ) + { + if (separator) + { + // parameter separator + + // check we are expecting multiple parameters + if (m_operatorStack[ m_operatorStackPtr ].parameterCount == 0) + { + throw AsmException_SyntaxError_ParameterCount( m_line, m_column - 1 ); + } + m_operatorStack[ m_operatorStackPtr ].parameterCount--; + + // put the open bracket back on the stack + m_operatorStackPtr++; + + // expect the next parameter + expected = VALUE_OR_UNARY; + } + else + { + // close par + + // check all parameters have been supplied + if (m_operatorStack[ m_operatorStackPtr ].parameterCount != 0) + { + throw AsmException_SyntaxError_ParameterCount( m_line, m_column - 1 ); + } + } + } + else { + // did not find matching bracket if ( bAllowOneMismatchedCloseBracket ) { // this is a hack which allows an extra close bracket to terminate an expression, @@ -1422,3 +1472,112 @@ void LineParser::EvalVal() m_valueStack[ m_valueStackPtr - 1 ] = value; } + + +/*************************************************************************************************/ +/** + LineParser::EvalLen() +*/ +/*************************************************************************************************/ +void LineParser::EvalLen() +{ + String str = StackTopString(); + m_valueStack[ m_valueStackPtr - 1 ] = str.Length(); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalChr() +*/ +/*************************************************************************************************/ +void LineParser::EvalChr() +{ + double value = StackTopNumber(); + int ascii = static_cast<int>(value); + if ((ascii < 0) || (ascii > 255)) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + char buffer = ascii; + m_valueStack[ m_valueStackPtr - 1 ] = String(&buffer, 1); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalAsc() +*/ +/*************************************************************************************************/ +void LineParser::EvalAsc() +{ + String str = StackTopString(); + if (str.Length() == 0) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + + m_valueStack[ m_valueStackPtr - 1 ] = static_cast<unsigned char>(str[0]); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalMid() +*/ +/*************************************************************************************************/ +void LineParser::EvalMid() +{ + if ( m_valueStackPtr < 3 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value1 = m_valueStack[ m_valueStackPtr - 3 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value3 = m_valueStack[ m_valueStackPtr - 1 ]; + if ((value1.GetType() != Value::StringValue) || (value2.GetType() != Value::NumberValue) || (value3.GetType() != Value::NumberValue)) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + m_valueStackPtr -= 2; + + String text = value1.GetString(); + int index = static_cast<int>(value2.GetNumber()) - 1; + int length = static_cast<int>(value3.GetNumber()); + if ((index < 0) || (static_cast<unsigned int>(index) > text.Length()) || (length < 0)) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + + m_valueStack[ m_valueStackPtr - 1 ] = text.SubString(index, length); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalString() +*/ +/*************************************************************************************************/ +void LineParser::EvalString() +{ + if ( m_valueStackPtr < 2 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value1 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 1 ]; + if ((value1.GetType() != Value::NumberValue) || (value2.GetType() != Value::StringValue)) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + m_valueStackPtr -= 1; + + int count = static_cast<int>(value1.GetNumber()); + String text = value2.GetString(); + if ((count < 0) || (count >= 0x10000) || (text.Length() >= 0x10000) || (static_cast<unsigned int>(count) * text.Length() >= 0x10000)) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + + m_valueStack[ m_valueStackPtr - 1 ] = text.Repeat(count); +} diff --git a/src/lineparser.cpp b/src/lineparser.cpp index c4499d6..92b3130 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -371,7 +371,7 @@ void LineParser::SkipStatement() /*************************************************************************************************/ void LineParser::SkipExpression( int bracketCount, bool bAllowOneMismatchedCloseBracket ) { - while ( AdvanceAndCheckEndOfSubStatement() ) + while ( AdvanceAndCheckEndOfSubStatement(bracketCount == 0) ) { if ( bAllowOneMismatchedCloseBracket ) { @@ -514,9 +514,16 @@ bool LineParser::AdvanceAndCheckEndOfStatement() @return bool true if we are not yet at the end of the substatement */ /*************************************************************************************************/ -bool LineParser::AdvanceAndCheckEndOfSubStatement() +bool LineParser::AdvanceAndCheckEndOfSubStatement(bool includeComma) { - return MoveToNextAtom( ";:\\,{}" ); + if (includeComma) + { + return MoveToNextAtom( ";:\\,{}" ); + } + else + { + return MoveToNextAtom( ";:\\{}" ); + } } diff --git a/src/lineparser.h b/src/lineparser.h index d568f4c..a2a65e3 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -91,6 +91,7 @@ class LineParser { const char* token; int precedence; + int parameterCount; OperatorHandler handler; }; @@ -110,7 +111,7 @@ class LineParser bool MoveToNextAtom( const char* pTerminators = NULL ); bool AdvanceAndCheckEndOfLine(); bool AdvanceAndCheckEndOfStatement(); - bool AdvanceAndCheckEndOfSubStatement(); + bool AdvanceAndCheckEndOfSubStatement(bool includeComma); void SkipStatement(); void SkipExpression( int bracketCount, bool bAllowOneMismatchedCloseBracket ); std::string GetSymbolName(); @@ -220,6 +221,11 @@ class LineParser void EvalTime(); void EvalStr(); void EvalVal(); + void EvalLen(); + void EvalChr(); + void EvalAsc(); + void EvalMid(); + void EvalString(); Value FormatAssemblyTime(const char* formatString); diff --git a/src/value.h b/src/value.h index d640777..e7e6783 100644 --- a/src/value.h +++ b/src/value.h @@ -87,6 +87,38 @@ struct StringHeader buffer[length] = 0; return header; } + + static StringHeader* SubString(StringHeader* source, unsigned int index, unsigned int length) + { + assert(index <= Length(source)); + unsigned int realLength = std::min(length, Length(source) - index); + if ((index == 0) && (realLength == Length(source))) + { + return source; + } + return Allocate(StringData(source) + index, realLength); + } + + static StringHeader* Repeat(StringHeader* source, unsigned int count) + { + int sourceLength = Length(source); + const char* sourceData = StringData(source); + + assert((sourceLength < 0x10000) && (count < 0x10000)); + unsigned int length = sourceLength * count; + assert(length < 0x10000); + + StringHeader* header = Allocate(length); + char* buffer = StringBuffer(header); + for (unsigned int i = 0; i != count; ++i) + { + memcpy(buffer, sourceData, sourceLength); + buffer += sourceLength; + } + *buffer = 0; + return header; + } + private: static char* StringBuffer(StringHeader* header) { @@ -148,6 +180,16 @@ class String StringHeader* header = StringHeader::Concat(m_header, that.m_header); return String(header); } + String SubString(unsigned int index, unsigned int length) + { + StringHeader* header = StringHeader::SubString(m_header, index, length); + return String(header); + } + String Repeat(unsigned int count) + { + StringHeader* header = StringHeader::Repeat(m_header, count); + return String(header); + } unsigned int Length() const { return StringHeader::Length(m_header); From 9cbadea53b7a6645e45852693e7d85133126a2bc Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 16:53:45 +0100 Subject: [PATCH 041/144] Removed superfluous class name prefixes in header This worked in VS2010 but gives an error in newer compilers. --- src/lineparser.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lineparser.h b/src/lineparser.h index a2a65e3..2cb937f 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -173,8 +173,8 @@ class LineParser // convenience functions for getting operator parameters from the stack std::pair<Value, Value> StackTopTwoValues(); - String LineParser::StackTopString(); - double LineParser::StackTopNumber(); + String StackTopString(); + double StackTopNumber(); std::pair<double, double> StackTopTwoNumbers(); std::pair<int, int> StackTopTwoInts(); From c214f073266c19fc9c3cc2cb181315ff1d89e09b Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 16:59:28 +0100 Subject: [PATCH 042/144] Signed/unsigned mismatch causing build failure --- src/commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.cpp b/src/commands.cpp index ab0f6d4..e24f58c 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1541,7 +1541,7 @@ void LineParser::HandlePrint() { String text = value.GetString(); const char* pstr = text.Text(); - for (int i = 0; i != text.Length(); ++i) + for (unsigned int i = 0; i != text.Length(); ++i) { cout << *pstr; ++pstr; From d1666cf703f8234cb4080cf9325e5bc82edbf99b Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 17:02:44 +0100 Subject: [PATCH 043/144] Missing include --- src/value.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/value.h b/src/value.h index e7e6783..36a396a 100644 --- a/src/value.h +++ b/src/value.h @@ -24,6 +24,7 @@ #define VALUE_H_ #include <cassert> +#include <cstring> // A simple immutable string buffer with a length and a reference count. // This doesn't have constructors, etc. so it can be stuffed into a union. From b8ad65339246cb01e98ebae38a665818ce6f2914 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 17:09:07 +0100 Subject: [PATCH 044/144] Included <cstring> rather than <string.h> --- src/value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value.h b/src/value.h index 36a396a..203a737 100644 --- a/src/value.h +++ b/src/value.h @@ -24,7 +24,7 @@ #define VALUE_H_ #include <cassert> -#include <cstring> +#include <string.h> // A simple immutable string buffer with a length and a reference count. // This doesn't have constructors, etc. so it can be stuffed into a union. From 88aad92644fcd2f822daadae7d18eed7b895709c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 19:58:19 +0100 Subject: [PATCH 045/144] Define string symbols on the command-line with -S --- README.md | 6 ++++-- src/main.cpp | 18 +++++++++++++++++- src/symboltable.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/symboltable.h | 1 + 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26c98af..7f17d2e 100644 --- a/README.md +++ b/README.md @@ -179,10 +179,12 @@ Things like `STA&4000` are permitted with or without `-w`. `-D <symbol>=<value>` +`-S <symbol>=<string>' + Define `<symbol>` before starting to assemble. If `<value>` is not given, `-1` (`TRUE`) -will be used by default. Note that there must be a space between `-D` and the symbol. `<value>` may be in decimal, hexadecimal (prefixed with $, & or 0x) or binary (prefixed with %). +will be used by default. Note that there must be a space between `-D` or `-S` and the symbol. `<value>` may be in decimal, hexadecimal (prefixed with $, & or 0x) or binary (prefixed with %). `<string>` may be quoted. -`-D` can be used in conjunction with conditional assignment to provide default values within the source which can be overridden from the command line. +`-D` and '-S' can be used in conjunction with conditional assignment to provide default values within the source which can be overridden from the command line. ## 5. SOURCE FILE SYNTAX diff --git a/src/main.cpp b/src/main.cpp index a36417c..91ea4e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,6 +76,7 @@ int main( int argc, char* argv[] ) WAITING_FOR_DISC_TITLE, WAITING_FOR_DISC_WRITES, WAITING_FOR_SYMBOL, + WAITING_FOR_STRING_SYMBOL, WAITING_FOR_LABELS_FILE } state = READY; @@ -154,6 +155,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_SYMBOL; } + else if ( strcmp( argv[i], "-S" ) == 0 ) + { + state = WAITING_FOR_STRING_SYMBOL; + } else if ( ( strcmp( argv[i], "--help" ) == 0 ) || ( strcmp( argv[i], "-help" ) == 0 ) || ( strcmp( argv[i], "-h" ) == 0 ) ) @@ -174,7 +179,8 @@ int main( int argc, char* argv[] ) cout << " -dd Dump all global and local symbols after assembly" << endl; cout << " -w Require whitespace between opcodes and labels" << endl; cout << " -vc Use Visual C++-style error messages" << endl; - cout << " -D <sym>=<val> Define symbol prior to assembly" << endl; + cout << " -D <sym>=<val> Define numeric symbol prior to assembly" << endl; + cout << " -S <sym>=<str> Define string symbol prior to assembly" << endl; cout << " --help See this help again" << endl; return EXIT_SUCCESS; } @@ -256,6 +262,16 @@ int main( int argc, char* argv[] ) state = READY; break; + case WAITING_FOR_STRING_SYMBOL: + + if ( ! SymbolTable::Instance().AddCommandLineStringSymbol( argv[i] ) ) + { + cerr << "Invalid -S expression: " << argv[i] << endl; + return EXIT_FAILURE; + } + state = READY; + break; + case WAITING_FOR_LABELS_FILE: pLabelsOutputFile = argv[i]; diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 54f5d68..b4ea003 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -263,6 +263,42 @@ bool SymbolTable::AddCommandLineSymbol( const std::string& expr ) +/*************************************************************************************************/ +/** + SymbolTable::AddCommandLineStringSymbol() + + Adds a string symbol to the symbol table using a command line 'FOO=BAR' expression + + @param expr Symbol name and value + @returns bool +*/ +/*************************************************************************************************/ +bool SymbolTable::AddCommandLineStringSymbol( const std::string& expr ) +{ + std::string::size_type equalsIndex = expr.find( '=' ); + std::string symbol; + std::string valueString; + if ( equalsIndex == std::string::npos ) + { + return false; + } + + symbol = expr.substr( 0, equalsIndex ); + valueString = expr.substr( equalsIndex + 1 ); + + if ( symbol.empty() ) + { + return false; + } + + String value = String(valueString.data(), valueString.length()); + + m_map.insert( make_pair( symbol, Symbol( value, false ) ) ); + + return true; +} + + /*************************************************************************************************/ /** SymbolTable::GetSymbol() diff --git a/src/symboltable.h b/src/symboltable.h index e7c4ba2..d1e7f43 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -41,6 +41,7 @@ class SymbolTable void AddSymbol( const std::string& symbol, Value value, bool isLabel = false ); bool AddCommandLineSymbol( const std::string& expr ); + bool AddCommandLineStringSymbol( const std::string& expr ); void ChangeSymbol( const std::string& symbol, double value ); Value GetSymbol( const std::string& symbol ) const; bool IsSymbolDefined( const std::string& symbol ) const; From 7b22c1ba26cb4d51c2c05576a36767b450459f87 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 20:15:37 +0100 Subject: [PATCH 046/144] Support symbol names ending in a '$' --- src/expression.cpp | 2 +- src/lineparser.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 6fe4928..5a41212 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -262,7 +262,7 @@ Value LineParser::GetValue() int oldColumn = m_column; string symbolName = GetSymbolName(); - if ((m_column < m_line.length()) && (m_line[m_column] == '$') && (symbolName == "TIME")) + if (symbolName == "TIME$") { // Handle TIME$ with no parameters diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 92b3130..5df7baa 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -98,8 +98,10 @@ void LineParser::Process() ( isalpha( m_line[ m_column ] ) || isdigit( m_line[ m_column ] ) || m_line[ m_column ] == '_' || - m_line[ m_column ] == '%' ) && - m_line[ m_column - 1 ] != '%' ); + m_line[ m_column ] == '%' || + m_line[ m_column ] == '$' ) && + m_line[ m_column - 1 ] != '%' && + m_line[ m_column - 1 ] != '$' ); if ( AdvanceAndCheckEndOfStatement() ) { @@ -550,8 +552,10 @@ string LineParser::GetSymbolName() ( isalpha( m_line[ m_column ] ) || isdigit( m_line[ m_column ] ) || m_line[ m_column ] == '_' || - m_line[ m_column ] == '%' ) && - m_line[ m_column - 1 ] != '%' ); + m_line[ m_column ] == '%' || + m_line[ m_column ] == '$' ) && + m_line[ m_column - 1 ] != '%' && + m_line[ m_column - 1 ] != '$' ); return symbolName; } From b8b2021ad45cb641fe3a976313304bc077d488bb Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 21:12:19 +0100 Subject: [PATCH 047/144] Typos in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f17d2e..9439e7e 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,12 @@ Things like `STA&4000` are permitted with or without `-w`. `-D <symbol>=<value>` -`-S <symbol>=<string>' +`-S <symbol>=<string>` Define `<symbol>` before starting to assemble. If `<value>` is not given, `-1` (`TRUE`) will be used by default. Note that there must be a space between `-D` or `-S` and the symbol. `<value>` may be in decimal, hexadecimal (prefixed with $, & or 0x) or binary (prefixed with %). `<string>` may be quoted. -`-D` and '-S' can be used in conjunction with conditional assignment to provide default values within the source which can be overridden from the command line. +`-D` and `-S` can be used in conjunction with conditional assignment to provide default values within the source which can be overridden from the command line. ## 5. SOURCE FILE SYNTAX From b60bb1ab0e3daf0884951f4209fa04e2cd0ce568 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 22:49:30 +0100 Subject: [PATCH 048/144] ASM command to assemble from strings The error reporting is poor for now. --- README.md | 4 ++++ src/assemble.cpp | 33 ++++++++++++++++++++++++++ src/commands.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++++++- src/lineparser.h | 2 ++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9439e7e..6297e2f 100644 --- a/README.md +++ b/README.md @@ -632,6 +632,10 @@ Abort assembly if any of the expressions is false. Seed the random number generator used by the RND() function. If this is not used, the random number generator is seeded based on the current time and so each build of a program using `RND()` will be different. +`ASM <instr>, <params>` + +Assemble the instruction with the supplied parameters. For example `ASM "LDA", "#&41"`. + ## 7. TIPS AND TRICKS BeebAsm's approach of treating memory as a canvas which can be written to, saved, and rewritten if desired makes it very easy to create certain types of applications. diff --git a/src/assemble.cpp b/src/assemble.cpp index 855b381..91ab02e 100644 --- a/src/assemble.cpp +++ b/src/assemble.cpp @@ -182,6 +182,39 @@ int LineParser::GetInstructionAndAdvanceColumn() } +/*************************************************************************************************/ +/** + LineParser::GetInstructionExact() + + Searches for an exact match for the supplied instruction. + + @param instr The string containing an instruction + + @return The token number, or -1 for "not found" + +*/ +/*************************************************************************************************/ +int LineParser::GetInstructionExact(const char* instr) +{ + for ( int i = 0; i < static_cast<int>( sizeof m_gaOpcodeTable / sizeof( OpcodeData ) ); i++ ) + { + int cpu = m_gaOpcodeTable[ i ].m_cpu; + const char* token = m_gaOpcodeTable[ i ].m_pName; + + // ignore instructions not for current cpu + if ( cpu > ObjectCode::Instance().GetCPU() ) + continue; + + // see if token matches + if (stricmp(instr, token) == 0) + { + return i; + } + } + + return -1; +} + /*************************************************************************************************/ /** diff --git a/src/commands.cpp b/src/commands.cpp index e24f58c..f913de0 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -81,7 +81,8 @@ const LineParser::Token LineParser::m_gaTokenTable[] = { "ENDMACRO", &LineParser::HandleEndMacro, &SourceFile::EndMacro }, { "ERROR", &LineParser::HandleError, 0 }, { "COPYBLOCK", &LineParser::HandleCopyBlock, 0 }, - { "RANDOMIZE", &LineParser::HandleRandomize, 0 } + { "RANDOMIZE", &LineParser::HandleRandomize, 0 }, + { "ASM", &LineParser::HandleAsm, 0 } }; @@ -2124,3 +2125,62 @@ void LineParser::HandleRandomize() throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); } } + + +/*************************************************************************************************/ +/** + LineParser::HandleAsm() +*/ +/*************************************************************************************************/ +void LineParser::HandleAsm() +{ + // look for mnemonic + + Value mnemonic = EvaluateExpression(); + + if (mnemonic.GetType() != Value::StringValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + + int instruction = GetInstructionExact(mnemonic.GetString().Text()); + if (instruction < 0) + { + throw AsmException_SyntaxError_UnrecognisedToken( m_line, m_column ); + } + + // look for comma + + if ( !AdvanceAndCheckEndOfStatement() ) + { + // found nothing + throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); + } + + if ( m_line[ m_column ] != ',' ) + { + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + } + + m_column++; + + // look for end value + + Value paramsValue = EvaluateExpression(); + + if (paramsValue.GetType() != Value::StringValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + + // check this is now the end + + if ( AdvanceAndCheckEndOfStatement() ) + { + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + } + + String params = paramsValue.GetString(); + LineParser parser(m_sourceCode, string(params.Text(), params.Length())); + parser.HandleAssembler(instruction); +} diff --git a/src/lineparser.h b/src/lineparser.h index 2cb937f..145ff03 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -107,6 +107,7 @@ class LineParser int GetTokenAndAdvanceColumn(); void HandleToken( int i, int oldColumn ); int GetInstructionAndAdvanceColumn(); + int GetInstructionExact(const char* instr); int CheckMacroMatches(); bool MoveToNextAtom( const char* pTerminators = NULL ); bool AdvanceAndCheckEndOfLine(); @@ -162,6 +163,7 @@ class LineParser void HandleError(); void HandleCopyBlock(); void HandleRandomize(); + void HandleAsm(); // expression evaluating methods From acf6fb3d719e564cc698879e01bbcf70b73bc4ea Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 23:04:01 +0100 Subject: [PATCH 049/144] Change stricmp to _stricmp --- src/assemble.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assemble.cpp b/src/assemble.cpp index 91ab02e..ca1742b 100644 --- a/src/assemble.cpp +++ b/src/assemble.cpp @@ -206,7 +206,7 @@ int LineParser::GetInstructionExact(const char* instr) continue; // see if token matches - if (stricmp(instr, token) == 0) + if (_stricmp(instr, token) == 0) { return i; } From 94fe423688fe363ba79b68f125c597a355712df1 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 1 Jul 2021 23:13:39 +0100 Subject: [PATCH 050/144] stricmp is MS-specific. Use a loop. --- src/assemble.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/assemble.cpp b/src/assemble.cpp index ca1742b..f46a493 100644 --- a/src/assemble.cpp +++ b/src/assemble.cpp @@ -200,13 +200,23 @@ int LineParser::GetInstructionExact(const char* instr) { int cpu = m_gaOpcodeTable[ i ].m_cpu; const char* token = m_gaOpcodeTable[ i ].m_pName; + size_t len = strlen( token ); // ignore instructions not for current cpu if ( cpu > ObjectCode::Instance().GetCPU() ) continue; - // see if token matches - if (_stricmp(instr, token) == 0) + bool bMatch = true; + for ( unsigned int j = 0; j <= len; j++ ) + { + if ( token[ j ] != toupper( instr[ j ] ) ) + { + bMatch = false; + break; + } + } + + if (bMatch) { return i; } From 024ff754b638be3b2bbde3e4dfb1385ebb8aeeb0 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 2 Jul 2021 11:11:10 +0100 Subject: [PATCH 051/144] UPPER and LOWER. Stub for EVAL. --- src/expression.cpp | 43 +++++++++++++++++++++- src/lineparser.h | 3 ++ src/value.h | 92 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 128 insertions(+), 10 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 5a41212..8846e17 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -105,11 +105,14 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "TIME$(", 10, 1, &LineParser::EvalTime }, { "STR$(", 10, 1, &LineParser::EvalStr }, { "VAL(", 10, 1, &LineParser::EvalVal }, + { "EVAL(", 10, 1, &LineParser::EvalEval }, { "LEN(", 10, 1, &LineParser::EvalLen }, { "CHR$(", 10, 1, &LineParser::EvalChr }, { "ASC(", 10, 1, &LineParser::EvalAsc }, { "MID$(", 10, 3, &LineParser::EvalMid }, - { "STRING$(", 10, 2, &LineParser::EvalString } + { "STRING$(", 10, 2, &LineParser::EvalString }, + { "UPPER(", 10, 1, &LineParser::EvalUpper }, + { "LOWER(", 10, 1, &LineParser::EvalLower } }; @@ -1474,6 +1477,22 @@ void LineParser::EvalVal() } +/*************************************************************************************************/ +/** + LineParser::EvalEval() +*/ +/*************************************************************************************************/ +void LineParser::EvalEval() +{ + String str = StackTopString(); + // TODO: Evaluate full expression + char* end; + double value = strtod(str.Text(), &end); + + m_valueStack[ m_valueStackPtr - 1 ] = value; +} + + /*************************************************************************************************/ /** LineParser::EvalLen() @@ -1581,3 +1600,25 @@ void LineParser::EvalString() m_valueStack[ m_valueStackPtr - 1 ] = text.Repeat(count); } + + +/*************************************************************************************************/ +/** + LineParser::EvalUpper() +*/ +/*************************************************************************************************/ +void LineParser::EvalUpper() +{ + m_valueStack[ m_valueStackPtr - 1 ] = StackTopString().Upper(); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalLower() +*/ +/*************************************************************************************************/ +void LineParser::EvalLower() +{ + m_valueStack[ m_valueStackPtr - 1 ] = StackTopString().Lower(); +} diff --git a/src/lineparser.h b/src/lineparser.h index 145ff03..c5f1730 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -223,11 +223,14 @@ class LineParser void EvalTime(); void EvalStr(); void EvalVal(); + void EvalEval(); void EvalLen(); void EvalChr(); void EvalAsc(); void EvalMid(); void EvalString(); + void EvalUpper(); + void EvalLower(); Value FormatAssemblyTime(const char* formatString); diff --git a/src/value.h b/src/value.h index 203a737..a0e0040 100644 --- a/src/value.h +++ b/src/value.h @@ -82,10 +82,13 @@ struct StringHeader { int length = header1->m_length + header2->m_length; StringHeader* header = Allocate(length); - char* buffer = StringBuffer(header); - memcpy(buffer, StringData(header1), header1->m_length); - memcpy(buffer + header1->m_length, StringData(header2), header2->m_length); - buffer[length] = 0; + if (header) + { + char* buffer = StringBuffer(header); + memcpy(buffer, StringData(header1), header1->m_length); + memcpy(buffer + header1->m_length, StringData(header2), header2->m_length); + buffer[length] = 0; + } return header; } @@ -110,16 +113,51 @@ struct StringHeader assert(length < 0x10000); StringHeader* header = Allocate(length); - char* buffer = StringBuffer(header); - for (unsigned int i = 0; i != count; ++i) + if (header) { - memcpy(buffer, sourceData, sourceLength); - buffer += sourceLength; + char* buffer = StringBuffer(header); + for (unsigned int i = 0; i != count; ++i) + { + memcpy(buffer, sourceData, sourceLength); + buffer += sourceLength; + } + *buffer = 0; } - *buffer = 0; return header; } + static StringHeader* Upper(StringHeader* source) + { + StringHeader* result = Copy(source); + if (result) + { + unsigned int length = Length(result); + char* pdata = StringBuffer(result); + for (unsigned int i = 0; i != length; ++i) + { + *pdata = ascii_upper(*pdata); + ++pdata; + } + } + return result; + } + + static StringHeader* Lower(StringHeader* source) + { + StringHeader* result = Copy(source); + if (result) + { + unsigned int length = Length(result); + char* pdata = StringBuffer(result); + for (unsigned int i = 0; i != length; ++i) + { + *pdata = ascii_lower(*pdata); + ++pdata; + } + } + return result; + } + private: static char* StringBuffer(StringHeader* header) { @@ -137,6 +175,11 @@ struct StringHeader return header; } + static StringHeader* Copy(StringHeader* source) + { + return Allocate(StringData(source), Length(source)); + } + static int Compare(unsigned int a, unsigned int b) { if (a == b) @@ -146,6 +189,25 @@ struct StringHeader else return 1; } + + static char ascii_lower(char c) + { + if (('A' <= c) && (c <= 'Z')) + { + c += 'a' - 'A'; + } + return c; + } + + static char ascii_upper(char c) + { + if (('a' <= c) && (c <= 'z')) + { + c -= 'a' - 'A'; + } + return c; + } + }; // A simple immutable string. @@ -191,6 +253,14 @@ class String StringHeader* header = StringHeader::Repeat(m_header, count); return String(header); } + String Upper() + { + return String(StringHeader::Upper(m_header)); + } + String Lower() + { + return String(StringHeader::Lower(m_header)); + } unsigned int Length() const { return StringHeader::Length(m_header); @@ -210,6 +280,10 @@ class String String(StringHeader* header) { + if (!header) + { + throw std::bad_alloc(); + } m_header = header; StringHeader::AddRef(m_header); } From 4f997dd9f42abc48cfb0517e45b0017d9c8e1120 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 2 Jul 2021 13:53:58 +0100 Subject: [PATCH 052/144] ASM now takes a single string parameter. --- README.md | 4 +-- src/assemble.cpp | 66 +++++++++++++----------------------------------- src/commands.cpp | 44 ++++++++------------------------ src/lineparser.h | 2 +- 4 files changed, 32 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 6297e2f..a6d78b5 100644 --- a/README.md +++ b/README.md @@ -632,9 +632,9 @@ Abort assembly if any of the expressions is false. Seed the random number generator used by the RND() function. If this is not used, the random number generator is seeded based on the current time and so each build of a program using `RND()` will be different. -`ASM <instr>, <params>` +`ASM <str>` -Assemble the instruction with the supplied parameters. For example `ASM "LDA", "#&41"`. +Assemble the supplied assembly language string. For example `ASM "LDA #&41"`. ## 7. TIPS AND TRICKS diff --git a/src/assemble.cpp b/src/assemble.cpp index f46a493..79347a0 100644 --- a/src/assemble.cpp +++ b/src/assemble.cpp @@ -117,6 +117,21 @@ const LineParser::OpcodeData LineParser::m_gaOpcodeTable[] = #undef X +/*************************************************************************************************/ +/** + LineParser::GetInstructionAndAdvanceColumn() + + Searches for an instruction match in the current line, starting at the current column, + and moves the column pointer past the token + + @return The token number, or -1 for "not found" + column is modified to index the character after the token +*/ +/*************************************************************************************************/ +int LineParser::GetInstructionAndAdvanceColumn() +{ + return GetInstructionAndAdvanceColumn(GlobalData::Instance().RequireDistinctOpcodes()); +} /*************************************************************************************************/ /** @@ -125,14 +140,13 @@ const LineParser::OpcodeData LineParser::m_gaOpcodeTable[] = Searches for an instruction match in the current line, starting at the current column, and moves the column pointer past the token - @param line The string to parse - @param column The column to start from + @param requireDistinctOpcodes If true, check the opcode is a complete token @return The token number, or -1 for "not found" column is modified to index the character after the token */ /*************************************************************************************************/ -int LineParser::GetInstructionAndAdvanceColumn() +int LineParser::GetInstructionAndAdvanceColumn(bool requireDistinctOpcodes) { for ( int i = 0; i < static_cast<int>( sizeof m_gaOpcodeTable / sizeof( OpcodeData ) ); i++ ) { @@ -159,7 +173,7 @@ int LineParser::GetInstructionAndAdvanceColumn() // The token matches so far, but (optionally) check there's nothing after it; this prevents // false matches where a macro name begins with an opcode, at the cost of disallowing // things like "foo=&70:stafoo". - if ( GlobalData::Instance().RequireDistinctOpcodes() && bMatch ) + if ( requireDistinctOpcodes && bMatch ) { std::string::size_type k = m_column + len; if ( k < m_line.length() ) @@ -182,50 +196,6 @@ int LineParser::GetInstructionAndAdvanceColumn() } -/*************************************************************************************************/ -/** - LineParser::GetInstructionExact() - - Searches for an exact match for the supplied instruction. - - @param instr The string containing an instruction - - @return The token number, or -1 for "not found" - -*/ -/*************************************************************************************************/ -int LineParser::GetInstructionExact(const char* instr) -{ - for ( int i = 0; i < static_cast<int>( sizeof m_gaOpcodeTable / sizeof( OpcodeData ) ); i++ ) - { - int cpu = m_gaOpcodeTable[ i ].m_cpu; - const char* token = m_gaOpcodeTable[ i ].m_pName; - size_t len = strlen( token ); - - // ignore instructions not for current cpu - if ( cpu > ObjectCode::Instance().GetCPU() ) - continue; - - bool bMatch = true; - for ( unsigned int j = 0; j <= len; j++ ) - { - if ( token[ j ] != toupper( instr[ j ] ) ) - { - bMatch = false; - break; - } - } - - if (bMatch) - { - return i; - } - } - - return -1; -} - - /*************************************************************************************************/ /** LineParser::HasAddressingMode() diff --git a/src/commands.cpp b/src/commands.cpp index f913de0..bcbb76f 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -2134,53 +2134,31 @@ void LineParser::HandleRandomize() /*************************************************************************************************/ void LineParser::HandleAsm() { - // look for mnemonic + // look for assembly language string - Value mnemonic = EvaluateExpression(); + Value asmValue = EvaluateExpression(); - if (mnemonic.GetType() != Value::StringValue) + if (asmValue.GetType() != Value::StringValue) { throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); } - int instruction = GetInstructionExact(mnemonic.GetString().Text()); - if (instruction < 0) - { - throw AsmException_SyntaxError_UnrecognisedToken( m_line, m_column ); - } - - // look for comma - - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } + // check this is now the end - if ( m_line[ m_column ] != ',' ) + if ( AdvanceAndCheckEndOfStatement() ) { throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); } - m_column++; - - // look for end value - - Value paramsValue = EvaluateExpression(); - - if (paramsValue.GetType() != Value::StringValue) - { - throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); - } - - // check this is now the end + String assembly = asmValue.GetString(); + LineParser parser(m_sourceCode, string(assembly.Text(), assembly.Length())); - if ( AdvanceAndCheckEndOfStatement() ) + // Parse the mnemonic, don't require a non-alpha after it. + int instruction = parser.GetInstructionAndAdvanceColumn(false); + if (instruction < 0) { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + throw AsmException_SyntaxError_UnrecognisedToken( m_line, m_column ); } - String params = paramsValue.GetString(); - LineParser parser(m_sourceCode, string(params.Text(), params.Length())); parser.HandleAssembler(instruction); } diff --git a/src/lineparser.h b/src/lineparser.h index c5f1730..6bd98cd 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -107,7 +107,7 @@ class LineParser int GetTokenAndAdvanceColumn(); void HandleToken( int i, int oldColumn ); int GetInstructionAndAdvanceColumn(); - int GetInstructionExact(const char* instr); + int GetInstructionAndAdvanceColumn(bool requireDistinctOpcodes); int CheckMacroMatches(); bool MoveToNextAtom( const char* pTerminators = NULL ); bool AdvanceAndCheckEndOfLine(); From c69d5c61d77c8801af32ff92e76e88863c295a48 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 2 Jul 2021 14:00:43 +0100 Subject: [PATCH 053/144] EVAL --- README.md | 1 + src/commands.cpp | 2 ++ src/expression.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6d78b5..1bd1a39 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,7 @@ LOG(val) Return the base 10 log of val LN(val) Return the natural log of val EXP(val) Return e raised to the power of val VAL(str) Return the value of a decimal number in a string +EVAL(str) Return the value of an expression in a string STR$(val) Return the number val converted to a string LEN(str) Return the length of str CHR$(val) Return a string with a single character with ASCII value val diff --git a/src/commands.cpp b/src/commands.cpp index bcbb76f..a01608a 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -2134,6 +2134,8 @@ void LineParser::HandleRandomize() /*************************************************************************************************/ void LineParser::HandleAsm() { + // TODO: Better error reporting + // look for assembly language string Value asmValue = EvaluateExpression(); diff --git a/src/expression.cpp b/src/expression.cpp index 8846e17..74aa5ee 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -1484,12 +1484,12 @@ void LineParser::EvalVal() /*************************************************************************************************/ void LineParser::EvalEval() { - String str = StackTopString(); - // TODO: Evaluate full expression - char* end; - double value = strtod(str.Text(), &end); + // TODO: Better error reporting - m_valueStack[ m_valueStackPtr - 1 ] = value; + String expr = StackTopString(); + LineParser parser(m_sourceCode, string(expr.Text(), expr.Length())); + Value result = parser.EvaluateExpression(); + m_valueStack[ m_valueStackPtr - 1 ] = result; } From 72ebafd09c9b279807f3c184c571ea3f1fe09444 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 2 Jul 2021 15:11:36 +0100 Subject: [PATCH 054/144] Removed redundant comments --- src/commands.cpp | 2 -- src/expression.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index a01608a..bcbb76f 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -2134,8 +2134,6 @@ void LineParser::HandleRandomize() /*************************************************************************************************/ void LineParser::HandleAsm() { - // TODO: Better error reporting - // look for assembly language string Value asmValue = EvaluateExpression(); diff --git a/src/expression.cpp b/src/expression.cpp index 74aa5ee..abda884 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -1484,8 +1484,6 @@ void LineParser::EvalVal() /*************************************************************************************************/ void LineParser::EvalEval() { - // TODO: Better error reporting - String expr = StackTopString(); LineParser parser(m_sourceCode, string(expr.Text(), expr.Length())); Value result = parser.EvaluateExpression(); From 3d3ce4b6db36659b37a63f91f254d3ff64187dda Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 10:58:17 +0100 Subject: [PATCH 055/144] Added STR$~(), renamed funcs to UPPER$ and LOWER$ --- README.md | 3 +++ src/expression.cpp | 20 ++++++++++++++++++-- src/lineparser.h | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1bd1a39..2a4c054 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ EXP(val) Return e raised to the power of val VAL(str) Return the value of a decimal number in a string EVAL(str) Return the value of an expression in a string STR$(val) Return the number val converted to a string +STR$~(val) Return the number val converted to a string in hexadecimal LEN(str) Return the length of str CHR$(val) Return a string with a single character with ASCII value val ASC(str) Return the ASCII value of the first character of str @@ -247,6 +248,8 @@ MID$(str,index,length) Return length characters of str starting at (one-based) index STRINGS$(count,str) Return str repeated count times +LOWER$(str) Return str converted to lowercase +UPPER$(str) Return str converted to uppercase TIME$ Return assembly date/time in format "Day,DD Mon Year.HH:MM:SS" TIME$("fmt") Return assembly date/time in a format determined by "fmt", which is the same format used by the C library strftime() diff --git a/src/expression.cpp b/src/expression.cpp index abda884..d19daac 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -104,6 +104,7 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "EXP(", 10, 1, &LineParser::EvalExp }, { "TIME$(", 10, 1, &LineParser::EvalTime }, { "STR$(", 10, 1, &LineParser::EvalStr }, + { "STR$~(", 10, 1, &LineParser::EvalStrHex }, { "VAL(", 10, 1, &LineParser::EvalVal }, { "EVAL(", 10, 1, &LineParser::EvalEval }, { "LEN(", 10, 1, &LineParser::EvalLen }, @@ -111,8 +112,8 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "ASC(", 10, 1, &LineParser::EvalAsc }, { "MID$(", 10, 3, &LineParser::EvalMid }, { "STRING$(", 10, 2, &LineParser::EvalString }, - { "UPPER(", 10, 1, &LineParser::EvalUpper }, - { "LOWER(", 10, 1, &LineParser::EvalLower } + { "UPPER$(", 10, 1, &LineParser::EvalUpper }, + { "LOWER$(", 10, 1, &LineParser::EvalLower } }; @@ -1462,6 +1463,21 @@ void LineParser::EvalStr() } +/*************************************************************************************************/ +/** + LineParser::EvalStrHex() +*/ +/*************************************************************************************************/ +void LineParser::EvalStrHex() +{ + ostringstream stream; + stream << std::hex << std::uppercase << static_cast<int>(StackTopNumber()); + string result = stream.str(); + + m_valueStack[ m_valueStackPtr - 1 ] = String(result.data(), result.length()); +} + + /*************************************************************************************************/ /** LineParser::EvalVal() diff --git a/src/lineparser.h b/src/lineparser.h index 6bd98cd..9e1e486 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -222,6 +222,7 @@ class LineParser void EvalExp(); void EvalTime(); void EvalStr(); + void EvalStrHex(); void EvalVal(); void EvalEval(); void EvalLen(); From 3147227f878da28b48827e51a819b2c2f3616591 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 12:09:02 +0100 Subject: [PATCH 056/144] Replaced ad hoc string parsers with EvaluateExpressionAsString --- src/commands.cpp | 290 ++++++++------------------------------------- src/expression.cpp | 19 +++ src/lineparser.cpp | 2 + src/lineparser.h | 1 + 4 files changed, 72 insertions(+), 240 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index bcbb76f..2f15dfe 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -597,37 +597,15 @@ void LineParser::HandleInclude() throw AsmException_SyntaxError_CantInclude( m_line, m_column ); } - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // string - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); + string filename = EvaluateExpressionAsString(); - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - else + if ( GlobalData::Instance().ShouldOutputAsm() ) { - string filename( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); - - if ( GlobalData::Instance().ShouldOutputAsm() ) - { - cerr << "Including file " << filename << endl; - } - - SourceFile input( filename.c_str(), m_sourceCode ); - input.Process(); + cerr << "Including file " << filename << endl; } - m_column = endQuotePos + 1; + SourceFile input( filename.c_str(), m_sourceCode ); + input.Process(); if ( AdvanceAndCheckEndOfStatement() ) { @@ -644,41 +622,19 @@ void LineParser::HandleInclude() /*************************************************************************************************/ void LineParser::HandleIncBin() { - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } + string filename = EvaluateExpressionAsString(); - if ( m_column >= m_line.length() || m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // string - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) + try { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); + ObjectCode::Instance().IncBin( filename.c_str() ); } - else + catch ( AsmException_AssembleError& e ) { - string filename( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); - - try - { - ObjectCode::Instance().IncBin( filename.c_str() ); - } - catch ( AsmException_AssembleError& e ) - { - e.SetString( m_line ); - e.SetColumn( m_column ); - throw; - } + e.SetString( m_line ); + e.SetColumn( m_column ); + throw; } - m_column = endQuotePos + 1; - if ( AdvanceAndCheckEndOfStatement() ) { throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); @@ -1051,45 +1007,22 @@ void LineParser::HandleSave() // syntax is SAVE "filename", start, end [, exec [, reload] ] + string saveFile = EvaluateExpressionAsString(); + if ( !AdvanceAndCheckEndOfStatement() ) { // found nothing throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); } - string saveFile; - - if ( m_line[ m_column ] == '\"' ) + if ( m_line[ m_column ] != ',' ) { - // get filename - - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - // did not find the end of the string - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - - saveFile = m_line.substr( m_column + 1, endQuotePos - m_column - 1 ); - - m_column = endQuotePos + 1; - - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; + // did not find a comma + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); } + m_column++; + // get start address start = EvaluateExpressionAsInt(); @@ -1438,26 +1371,6 @@ void LineParser::HandlePrint() { throw AsmException_SyntaxError_MissingComma( m_line, m_column ); } - else if ( m_line[ m_column ] == '\"' ) - { - // string - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - else - { - if ( GlobalData::Instance().IsSecondPass() ) - { - cout << m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) << " "; - } - } - - m_column = endQuotePos + 1; - bDemandComma = true; - } else if ( m_line[ m_column ] == '~' ) { // print in hex @@ -1595,31 +1508,11 @@ void LineParser::HandlePutFileCommon( bool bText ) // Syntax: // PUTFILE/PUTTEXT <host filename>, [<beeb filename>,] <start addr> [,<exec addr>] - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // get first filename - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - - string hostFilename( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); + string hostFilename = EvaluateExpressionAsString(); string beebFilename = hostFilename; int start = 0; int exec = 0; - m_column = endQuotePos + 1; - if ( !AdvanceAndCheckEndOfStatement() ) { throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); @@ -1632,32 +1525,14 @@ void LineParser::HandlePutFileCommon( bool bText ) m_column++; - if ( !AdvanceAndCheckEndOfStatement() ) + Value beebFileOrStartAddr = EvaluateExpression(); + if (beebFileOrStartAddr.GetType() == Value::NumberValue) { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); + start = static_cast<int>(beebFileOrStartAddr.GetNumber()); } - - if ( m_line[ m_column ] == '\"' ) + else if (beebFileOrStartAddr.GetType() == Value::StringValue) { - // string - endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - - // get the second filename parameter - - beebFilename = m_line.substr( m_column + 1, endQuotePos - m_column - 1 ); - - m_column = endQuotePos + 1; - - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } + beebFilename = string(beebFileOrStartAddr.GetString().Text(), beebFileOrStartAddr.GetString().Length()); if ( m_line[ m_column ] != ',' ) { @@ -1666,20 +1541,24 @@ void LineParser::HandlePutFileCommon( bool bText ) } m_column++; - } - // Get start address - - try - { - start = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsSecondPass() ) + // Get start address + try { - throw; + start = EvaluateExpressionAsInt(); } + catch ( AsmException_SyntaxError_SymbolNotDefined& ) + { + if ( GlobalData::Instance().IsSecondPass() ) + { + throw; + } + } + } + else + { + assert(false); + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); } exec = start; @@ -1789,29 +1668,9 @@ void LineParser::HandlePutFileCommon( bool bText ) /*************************************************************************************************/ void LineParser::HandlePutBasic() { - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // get first filename - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - - string hostFilename( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); + string hostFilename = EvaluateExpressionAsString(); string beebFilename = hostFilename; - m_column = endQuotePos + 1; - if ( AdvanceAndCheckEndOfStatement() ) { // see if there's a second parameter @@ -1823,29 +1682,7 @@ void LineParser::HandlePutBasic() m_column++; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // string - endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - - // get the second parameter - - beebFilename = m_line.substr( m_column + 1, endQuotePos - m_column - 1 ); - - m_column = endQuotePos + 1; + beebFilename = EvaluateExpressionAsString(); } // check this is now the end @@ -2003,38 +1840,17 @@ void LineParser::HandleError() { int oldColumn = m_column; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != '\"' ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // string - size_t endQuotePos = m_line.find_first_of( '\"', m_column + 1 ); - - if ( endQuotePos == string::npos ) - { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - else - { - string errorMsg( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); - - // throw error - - throw AsmException_UserError( m_line, oldColumn, errorMsg ); - } - - m_column = endQuotePos + 1; + string errorMsg = EvaluateExpressionAsString(); + // This is a slight change in behaviour. It used to check the statement + // was well-formed after the error was thrown. if ( AdvanceAndCheckEndOfStatement() ) { throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); } + + // throw error + throw AsmException_UserError( m_line, oldColumn, errorMsg ); } @@ -2136,12 +1952,7 @@ void LineParser::HandleAsm() { // look for assembly language string - Value asmValue = EvaluateExpression(); - - if (asmValue.GetType() != Value::StringValue) - { - throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); - } + string assembly = EvaluateExpressionAsString(); // check this is now the end @@ -2150,8 +1961,7 @@ void LineParser::HandleAsm() throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); } - String assembly = asmValue.GetString(); - LineParser parser(m_sourceCode, string(assembly.Text(), assembly.Length())); + LineParser parser(m_sourceCode, assembly); // Parse the mnemonic, don't require a non-alpha after it. int instruction = parser.GetInstructionAndAdvanceColumn(false); diff --git a/src/expression.cpp b/src/expression.cpp index d19daac..26ac383 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -675,6 +675,25 @@ unsigned int LineParser::EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatch } +/*************************************************************************************************/ +/** + LineParser::EvaluateExpressionAsString() + + Version of EvaluateExpression which returns its result as a String or throws a type mismatch +*/ +/*************************************************************************************************/ + string LineParser::EvaluateExpressionAsString( bool bAllowOneMismatchedCloseBracket ) +{ + Value value = EvaluateExpression( bAllowOneMismatchedCloseBracket ); + if (value.GetType() != Value::StringValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + String result = value.GetString(); + return string(result.Text(), result.Length()); +} + + /*************************************************************************************************/ /** LineParser::StackTopTwoValues() diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 5df7baa..3697848 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -330,6 +330,8 @@ void LineParser::SkipStatement() { if ( m_column < m_line.length() && m_line[ m_column ] == '\"' && !bInSingleQuotes ) { + // This handles quoted quotes in strings (like "a""b") because it views + // them as two adjacent strings. bInQuotes = !bInQuotes; } else if ( m_column < m_line.length() && m_line[ m_column ] == '\'' ) diff --git a/src/lineparser.h b/src/lineparser.h index 9e1e486..356284c 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -171,6 +171,7 @@ class LineParser double EvaluateExpressionAsDouble( bool bAllowOneMismatchedCloseBracket = false ); int EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket = false ); unsigned int EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatchedCloseBracket = false ); + std::string EvaluateExpressionAsString( bool bAllowOneMismatchedCloseBracket = false ); Value GetValue(); // convenience functions for getting operator parameters from the stack From 5df1b16d2dd482e23847c5e6a1425bce954f2af1 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 14:28:53 +0100 Subject: [PATCH 057/144] Improved ASM error message for missing instruction --- src/asmexception.h | 1 + src/commands.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/asmexception.h b/src/asmexception.h index 6af5d10..372d1d4 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -220,6 +220,7 @@ DEFINE_SYNTAX_EXCEPTION( BadAddress, "Out of range address." ); DEFINE_SYNTAX_EXCEPTION( BadIndexed, "Syntax error in indexed instruction." ); DEFINE_SYNTAX_EXCEPTION( NoIndexedX, "X indexed mode does not exist for this instruction." ); DEFINE_SYNTAX_EXCEPTION( NoIndexedY, "Y indexed mode does not exist for this instruction." ); +DEFINE_SYNTAX_EXCEPTION( MissingAssemblyInstruction, "Expected an assembly language instruction." ); DEFINE_SYNTAX_EXCEPTION( LabelAlreadyDefined, "Symbol already defined." ); DEFINE_SYNTAX_EXCEPTION( InvalidSymbolName, "Invalid symbol name; must start with a letter and contain only letters, numbers and underscore." ); DEFINE_SYNTAX_EXCEPTION( SymbolScopeOutsideMacro, "Symbol scope cannot promote outside current macro." ); diff --git a/src/commands.cpp b/src/commands.cpp index 2f15dfe..20732e2 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1967,7 +1967,7 @@ void LineParser::HandleAsm() int instruction = parser.GetInstructionAndAdvanceColumn(false); if (instruction < 0) { - throw AsmException_SyntaxError_UnrecognisedToken( m_line, m_column ); + throw AsmException_SyntaxError_MissingAssemblyInstruction( parser.m_line, parser.m_column ); } parser.HandleAssembler(instruction); From 687e833f4534a7b6572f921d09e5e1a0adb00a55 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 15:22:31 +0100 Subject: [PATCH 058/144] Parsing of TIME$ swallowed an extra character --- src/expression.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 26ac383..1f767bc 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -269,9 +269,6 @@ Value LineParser::GetValue() if (symbolName == "TIME$") { // Handle TIME$ with no parameters - - m_column++; - value = FormatAssemblyTime("%a,%d %b %Y.%H:%M:%S"); } else From f004af87cde5bcfe5fb019fda3676761686c40be Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 15:32:14 +0100 Subject: [PATCH 059/144] stringfunctions.6502 has example/test code --- examples/stringfunctions.6502 | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/stringfunctions.6502 diff --git a/examples/stringfunctions.6502 b/examples/stringfunctions.6502 new file mode 100644 index 0000000..b08eca0 --- /dev/null +++ b/examples/stringfunctions.6502 @@ -0,0 +1,26 @@ +org &2000 +.start + rts +.end + +save "test", start, end + +foo="Foo" +bar="Bar" +Foo="fooled!" +ASSERT foo + " " + bar = "Foo Bar" +ASSERT MID$(bar, 2, 1) = "a" +ASSERT UPPER$(foo) = "FOO" +ASSERT LOWER$(bar) = "bar" +ASSERT VAL("7.2") = 7.2 +ASSERT EVAL(foo) = "fooled!" +ASSERT STR$(7.2) = "7.2" +ASSERT STR$~(27.3) = "1B" +ASSERT LEN("") = 0 +ASSERT LEN(foo) = 3 +ASSERT LEN("a""b") = 3 +ASSERT CHR$(65) = "A" +ASSERT ASC(foo) = 70 +ASSERT ASC(MID$("a""b", 2, 1)) = '"' +ASSERT STRING$(3, bar) = bar + bar + bar +ASSERT TIME$ = TIME$("%a,%d %b %Y.%H:%M:%S") From 4e760c3c7ca7ceca3541458ffc2be68330a86537 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 17:03:30 +0100 Subject: [PATCH 060/144] Make filename parameter to SAVE optional again --- README.md | 4 ++-- src/commands.cpp | 35 ++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2a4c054..621ca8e 100644 --- a/README.md +++ b/README.md @@ -384,9 +384,9 @@ Puts a 'guard' on the specified address which will cause an error if you attempt Clears all guards between the `<start>` and `<end`> addresses specified. This can also be used to reset a section of memory which has had code assembled in it previously. BeebAsm will complain if you attempt to assemble code over previously assembled code at the same address without having `CLEAR`ed it first. -`SAVE "filename", start, end [, exec [, reload] ]` +`SAVE ["filename"], start, end [, exec [, reload] ]` -Saves out object code to either a DFS disc image (if one has been specified), or to the current directory as a standalone file. A source file must have at least one SAVE statement in it, otherwise nothing will be output. BeebAsm will warn if this is the case. +Saves out object code to either a DFS disc image (if one has been specified), or to the current directory as a standalone file. The filename is optional only if a name is specified with `-o` on the command line. A source file must have at least one SAVE statement in it, otherwise nothing will be output. BeebAsm will warn if this is the case. `'exec'` can be optionally specified as the execution address of the file when saved to a disc image. diff --git a/src/commands.cpp b/src/commands.cpp index 20732e2..d1cdb5f 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -998,6 +998,7 @@ void LineParser::HandleAssert() /*************************************************************************************************/ void LineParser::HandleSave() { + string saveFile; int start = 0; int end = 0; int exec = 0; @@ -1005,27 +1006,35 @@ void LineParser::HandleSave() int oldColumn = m_column; - // syntax is SAVE "filename", start, end [, exec [, reload] ] + // syntax is SAVE ["filename"], start, end [, exec [, reload] ] - string saveFile = EvaluateExpressionAsString(); + Value saveOrStart = EvaluateExpression(); - if ( !AdvanceAndCheckEndOfStatement() ) + if (saveOrStart.GetType() == Value::NumberValue) { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); + start = static_cast<int>(saveOrStart.GetNumber()); } - - if ( m_line[ m_column ] != ',' ) + else if (saveOrStart.GetType() == Value::StringValue) { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + saveFile = string(saveOrStart.GetString().Text(), saveOrStart.GetString().Length()); - m_column++; + if ( m_line[ m_column ] != ',' ) + { + // did not find a comma + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + } + + m_column++; - // get start address + // Get start address + start = EvaluateExpressionAsInt(); + } + else + { + assert(false); + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } - start = EvaluateExpressionAsInt(); exec = start; reload = start; From 7390eb81f06e351e14ece9bffde65d72d7d238a5 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 21:28:53 +0100 Subject: [PATCH 061/144] Failed recovery from undefined symbol in function in list When an undefined symbol is evaluated beebasm skips the rest of the expression and throws an exception. Previously, a comma was unequivocally the end of an expression but this is not true with multi-parameter functions. LineParser::SkipExpression needs to keep track of the bracket count to handle commas correctly. This only caused problems when an undefined symbol was surrounded with brackets (like in a function call), and there were multiple expressions in a comma-separated list, like in an EQUB command. --- src/lineparser.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 3697848..1dcec2b 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -377,20 +377,17 @@ void LineParser::SkipExpression( int bracketCount, bool bAllowOneMismatchedClose { while ( AdvanceAndCheckEndOfSubStatement(bracketCount == 0) ) { - if ( bAllowOneMismatchedCloseBracket ) + if ( m_line[ m_column ] == '(' ) { - if ( m_line[ m_column ] == '(' ) - { - bracketCount++; - } - else if ( m_line[ m_column ] == ')' ) - { - bracketCount--; + bracketCount++; + } + else if ( m_line[ m_column ] == ')' ) + { + bracketCount--; - if ( bracketCount < 0 ) - { - break; - } + if (bAllowOneMismatchedCloseBracket && ( bracketCount < 0 ) ) + { + break; } } From 60be8919c1a46907779fac0942477bf4dae4ef36 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 3 Jul 2021 23:29:49 +0100 Subject: [PATCH 062/144] Updated credits in README --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 621ca8e..3c7fb90 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ Note that it is not possible to reassign variables once defined. However `FOR... Variables can be defined if they are not already defined using the conditional assignment syntax `=?`, e.g. `addr =? &70`. This is useful in conjunction with the `-D` command line option to provide default values for variables in the source while allowing them to be overridden on the command line. (Because variables cannot be reassigned once defined, it is not possible to define a variable with `-D` *and* with non-conditional assignment.) -(Thanks to Stephen Harris <sweh@spuddy.org> and "ctr" for the `-D`/conditional assignment support.) +(Thanks to Stephen Harris <sweh@spuddy.org> and Charles Reilly for the `-D`/conditional assignment support.) ## 6. ASSEMBLER DIRECTIVES @@ -728,7 +728,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to cardboardguru76 for pointing this out). Fixed silently treating label references starting with "." - as 0 (thanks to mungre for this fix). + as 0 (thanks to Charles Reilly for this fix). Allowed "-h" and "-help" options to see help. Fixed tokenisation of BASIC pseudo-variables in some cases. (Thanks to Richard Russell for advice on this.) @@ -738,6 +738,9 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) Added -writes, -dd and -labels options (thanks to tricky for these) Added CMake support and man page (thanks to Dave Lambley) + Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, + STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with thanks to + Steven Flintham.) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From c6299d5f8850a607f160756bc07e65e0b0eae508 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 5 Jul 2021 18:48:40 +0100 Subject: [PATCH 063/144] Testing - README.md, testrunner.py and existing examples --- test/README.md | 35 ++++++ test/assertdemo.fail.6502 | 34 ++++++ test/autolinenumdemo.6502 | 7 ++ test/autolinenumdemo.bas | 20 ++++ test/basicrhstoken.6502 | 1 + test/basicrhstoken.bas | 13 +++ test/callstackdemo.fail.6502 | 22 ++++ test/errorlinenumber1.fail.6502 | 17 +++ test/errorlinenumber2.fail.6502 | 15 +++ test/filelinecallstackdemo.6502 | 27 +++++ test/invalidbasic1.bas | 1 + test/invalidbasic1.fail.6502 | 9 ++ test/invalidbasic2.bas | 1 + test/invalidbasic2.fail.6502 | 9 ++ test/local-forward-branch-1.6502 | 20 ++++ test/local-forward-branch-2.6502 | 24 +++++ test/local-forward-branch-3.6502 | 15 +++ test/local-forward-branch-4.6502 | 19 ++++ test/local-forward-branch-5.6502 | 30 ++++++ test/putbasicnonexistentdemo.fail.6502 | 9 ++ test/putfilenonexistentdemo.fail.6502 | 7 ++ test/scopejumpdemo1.6502 | 27 +++++ test/scopejumpdemo2.6502 | 19 ++++ test/scopejumpdemo2.inc.6502 | 49 +++++++++ test/stringfunctions.6502 | 26 +++++ test/testrunner.py | 142 +++++++++++++++++++++++++ 26 files changed, 598 insertions(+) create mode 100644 test/README.md create mode 100644 test/assertdemo.fail.6502 create mode 100644 test/autolinenumdemo.6502 create mode 100644 test/autolinenumdemo.bas create mode 100644 test/basicrhstoken.6502 create mode 100644 test/basicrhstoken.bas create mode 100644 test/callstackdemo.fail.6502 create mode 100644 test/errorlinenumber1.fail.6502 create mode 100644 test/errorlinenumber2.fail.6502 create mode 100644 test/filelinecallstackdemo.6502 create mode 100644 test/invalidbasic1.bas create mode 100644 test/invalidbasic1.fail.6502 create mode 100644 test/invalidbasic2.bas create mode 100644 test/invalidbasic2.fail.6502 create mode 100644 test/local-forward-branch-1.6502 create mode 100644 test/local-forward-branch-2.6502 create mode 100644 test/local-forward-branch-3.6502 create mode 100644 test/local-forward-branch-4.6502 create mode 100644 test/local-forward-branch-5.6502 create mode 100644 test/putbasicnonexistentdemo.fail.6502 create mode 100644 test/putfilenonexistentdemo.fail.6502 create mode 100644 test/scopejumpdemo1.6502 create mode 100644 test/scopejumpdemo2.6502 create mode 100644 test/scopejumpdemo2.inc.6502 create mode 100644 test/stringfunctions.6502 create mode 100644 test/testrunner.py diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..d444b27 --- /dev/null +++ b/test/README.md @@ -0,0 +1,35 @@ +# Testing + +This directory contains tests for beebasm. They require python3. + +Run the tests from the directory above using either +`python test/testrunner.py` or `python3 test/testrunner.py`. + +# Tests + +The test runner scans `test` and any subdirectories for files with a +`.6502` extension. Subdirectories are scanned in alphabetical order to +allow simpler tests to be prioritised. + +It distinguishes between include files (`.inc.6502`), failure tests +(`.fail.6502`) and success tests (`.6502`). Include files are ignored. +Failure and success tests are assembled with beebasm. + +The first line of a `.6502` file can be a comment with extra +command-line options to pass to beebasm. For example: + +``` +\ beebasm -do test.ssd +``` + +The `-v` and `-i` options are always set by the test runner. + +If a test file has a corresponding `.gold.ssd` file this is assumed to be +known-good output from running the test. The test runner will add the +`-do` option to the command-line. For success tests, if will also check +that the `.ssd` produced by the test is identical to the gold ssd. + +For example, if a directory contains `test.6502` and `test.gold.ssd` then +the test will be required to produce a `test.ssd` file that is identical +to `test.gold.ssd`. + diff --git a/test/assertdemo.fail.6502 b/test/assertdemo.fail.6502 new file mode 100644 index 0000000..6ac5f66 --- /dev/null +++ b/test/assertdemo.fail.6502 @@ -0,0 +1,34 @@ +org &2000 + +.start + assert (addr_offset + 3) <= 255 + + \ You can assert multiple things if you wish: + assert 65==65, loop == start + 4 + assert 1+2==3, 2+2 == 5 + + assert start == &2000 + + ldy #addr_offset + ldx #0 +.loop + lda (&70),y + sta &3000,x + iny + inx + cpx #4 + bne loop + rts + +.end + +org &9000 +.some_object +for i, 0, 253 + equb 0 +next +.addr + equd 0 +addr_offset = addr - some_object + +save "test", start, end diff --git a/test/autolinenumdemo.6502 b/test/autolinenumdemo.6502 new file mode 100644 index 0000000..881a32c --- /dev/null +++ b/test/autolinenumdemo.6502 @@ -0,0 +1,7 @@ +org &2000 +.start + rts +.end + +save "test", start, end +putbasic "autolinenumdemo.bas", "alndemo" diff --git a/test/autolinenumdemo.bas b/test/autolinenumdemo.bas new file mode 100644 index 0000000..9efd514 --- /dev/null +++ b/test/autolinenumdemo.bas @@ -0,0 +1,20 @@ +REM Line numbers are now optional in BASIC programs used with the putbasic +REM command. +100PRINT "but you can give them if you want, so the change is backwards-"; +110PRINT "compatible." +FOR I%=1 TO 10 +RESTORE (1000+RND(3)*10) +READ word$ +PRINT word$ +NEXT +PROCgoodbye +END +REM Apart from backwards compatibility, being able to specify line numbers +REM is handy for the rare cases where BASIC needs them, such as computed +REM RESTORE statements. +1010DATA foo +1020DATA bar +1030DATA baz +DEF PROCgoodbye +PRINT "TTFN" +ENDPROC diff --git a/test/basicrhstoken.6502 b/test/basicrhstoken.6502 new file mode 100644 index 0000000..e46a8c2 --- /dev/null +++ b/test/basicrhstoken.6502 @@ -0,0 +1 @@ +putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/test/basicrhstoken.bas b/test/basicrhstoken.bas new file mode 100644 index 0000000..f2d9a47 --- /dev/null +++ b/test/basicrhstoken.bas @@ -0,0 +1,13 @@ +p=PAGE +PRINT p +?&900=PAGE MOD 256 +PRINT FNpage +PRINT ?&900 +?&901=TIME MOD 256 +PRINT ?&901 +PRINT FNtime +END +DEF FNpage +=PAGE +DEF FNtime +=TIME diff --git a/test/callstackdemo.fail.6502 b/test/callstackdemo.fail.6502 new file mode 100644 index 0000000..3479313 --- /dev/null +++ b/test/callstackdemo.fail.6502 @@ -0,0 +1,22 @@ +org &2000 + +macro add n + clc + adc #n +endmacro + +macro add_twice n + add n + add n*2 +endmacro + +.start + lda &70 + add_twice 4 + add_twice 200 + sta &70 + rts + +.end + +save "test", start, end diff --git a/test/errorlinenumber1.fail.6502 b/test/errorlinenumber1.fail.6502 new file mode 100644 index 0000000..f7d1382 --- /dev/null +++ b/test/errorlinenumber1.fail.6502 @@ -0,0 +1,17 @@ +org &2000 +.start + +macro foo + for i, 0, 2 + nop + next + + lda #10 + ldx (&70),y +endmacro + +foo + +.end + +save "foo", start, end diff --git a/test/errorlinenumber2.fail.6502 b/test/errorlinenumber2.fail.6502 new file mode 100644 index 0000000..b585b14 --- /dev/null +++ b/test/errorlinenumber2.fail.6502 @@ -0,0 +1,15 @@ +org &2000 +.start + +for i, 0, 2 + nop +next + +lda #10 +ldx (&70),y + +foo + +.end + +save "foo", start, end diff --git a/test/filelinecallstackdemo.6502 b/test/filelinecallstackdemo.6502 new file mode 100644 index 0000000..fe441e8 --- /dev/null +++ b/test/filelinecallstackdemo.6502 @@ -0,0 +1,27 @@ +org &2000 + +macro foo n + bar n-1 +endmacro + +macro bar n + lda #n + print "Current call stack:", CALLSTACK$, " (end)" +endmacro + +.start + ldy #42 + print "Current location:", FILELINE$ + ldx #0 +.loop + foo 25 + sta &3000,x + iny + inx + cpx #4 + bne loop + rts + +.end + +save "test", start, end diff --git a/test/invalidbasic1.bas b/test/invalidbasic1.bas new file mode 100644 index 0000000..62eb4c3 --- /dev/null +++ b/test/invalidbasic1.bas @@ -0,0 +1 @@ +10PRINT "Hello diff --git a/test/invalidbasic1.fail.6502 b/test/invalidbasic1.fail.6502 new file mode 100644 index 0000000..e879ed6 --- /dev/null +++ b/test/invalidbasic1.fail.6502 @@ -0,0 +1,9 @@ +\ beebasm -do invalidbasic1.ssd + +org &2000 +.start + rts +.end + +save "test", start, end +putbasic "invalidbasic1.bas", "ib1" diff --git a/test/invalidbasic2.bas b/test/invalidbasic2.bas new file mode 100644 index 0000000..7482ead --- /dev/null +++ b/test/invalidbasic2.bas @@ -0,0 +1 @@ +10REM This line just goes on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on... diff --git a/test/invalidbasic2.fail.6502 b/test/invalidbasic2.fail.6502 new file mode 100644 index 0000000..34665e3 --- /dev/null +++ b/test/invalidbasic2.fail.6502 @@ -0,0 +1,9 @@ +\ beebasm -do invalidbasic2.ssd + +org &2000 +.start + rts +.end + +save "test", start, end +putbasic "invalidbasic2.bas", "ib2" diff --git a/test/local-forward-branch-1.6502 b/test/local-forward-branch-1.6502 new file mode 100644 index 0000000..161a6e2 --- /dev/null +++ b/test/local-forward-branch-1.6502 @@ -0,0 +1,20 @@ +org &2000 + +.start + +.ok + for i,1,65 + lda #65 + next + { + cmp #65:beq ok:jmp boom:.ok + } + rts + +.boom + lda #7:jsr &ffee + jmp boom + +.end + +save "test", start, end diff --git a/test/local-forward-branch-2.6502 b/test/local-forward-branch-2.6502 new file mode 100644 index 0000000..e46fd65 --- /dev/null +++ b/test/local-forward-branch-2.6502 @@ -0,0 +1,24 @@ +org &2000 + +macro branch_if_eq target + beq target +endmacro + +.start + +.ok + for i,1,65 + lda #65 + next + { + cmp #65:branch_if_eq ok:jmp boom:.ok + } + rts + +.boom + lda #7:jsr &ffee + jmp boom + +.end + +save "test", start, end diff --git a/test/local-forward-branch-3.6502 b/test/local-forward-branch-3.6502 new file mode 100644 index 0000000..758f8b1 --- /dev/null +++ b/test/local-forward-branch-3.6502 @@ -0,0 +1,15 @@ +org &2000 + +a = &70 + +macro foo zp + lda zp +endmacro + +.start + + foo a + +.end + +save "test", start, end diff --git a/test/local-forward-branch-4.6502 b/test/local-forward-branch-4.6502 new file mode 100644 index 0000000..aa06f44 --- /dev/null +++ b/test/local-forward-branch-4.6502 @@ -0,0 +1,19 @@ +org &2000 + +a = &70 + +macro load_a_and_maybe_x i, do_x + lda #i + if do_x + ldx #i + endif +endmacro + +.start + + load_a_and_maybe_x 42, FALSE + load_a_and_maybe_x 65, TRUE + +.end + +save "test", start, end diff --git a/test/local-forward-branch-5.6502 b/test/local-forward-branch-5.6502 new file mode 100644 index 0000000..852472f --- /dev/null +++ b/test/local-forward-branch-5.6502 @@ -0,0 +1,30 @@ +org &2000 + +\ Some buggy changes to the assembler caused the values emitted by equw +\ to be incorrect; no obvious failure occurred. So it's important to check +\ the output of this to see that it's right, unlike the earlier +\ local-forward-branch-n.6502 tests which simply failed to assemble. + +macro bar x + equw x +endmacro + +macro foo x + bar x-1 +endmacro + +.start + + lda #65 +.baz + + foo start ; should emit &ff &1f + foo baz ; should emit &01 &20 + + ldx #42 + + foo end ; should emit &09 &20 + +.end + +save "test", start, end diff --git a/test/putbasicnonexistentdemo.fail.6502 b/test/putbasicnonexistentdemo.fail.6502 new file mode 100644 index 0000000..0e3ca3d --- /dev/null +++ b/test/putbasicnonexistentdemo.fail.6502 @@ -0,0 +1,9 @@ +\ beebasm -do putbasicnonexistentdemo.ssd + +org &2000 +.start + rts +.end + +save "test", start, end +putbasic "nonexistent", "ne" diff --git a/test/putfilenonexistentdemo.fail.6502 b/test/putfilenonexistentdemo.fail.6502 new file mode 100644 index 0000000..fe9dcd8 --- /dev/null +++ b/test/putfilenonexistentdemo.fail.6502 @@ -0,0 +1,7 @@ +org &2000 +.start + rts +.end + +save "test", start, end +putfile "nonexistent", "ne", 0, 0 diff --git a/test/scopejumpdemo1.6502 b/test/scopejumpdemo1.6502 new file mode 100644 index 0000000..49223ef --- /dev/null +++ b/test/scopejumpdemo1.6502 @@ -0,0 +1,27 @@ +ORG &2000 + +.start + + { + LDX #255 + .loop + DEX + BNE loop + } + + { + \ .loop128 is a global label and is visible outside of this scope + .*loop128 + LDX #128 + \ This .loop label here is independent of the previous one + .loop + DEX + BNE loop + } + + \ JMP loop - this is an error, there is no .loop label visible + JMP loop128 \ this is fine + +.end + +SAVE "test", start, end diff --git a/test/scopejumpdemo2.6502 b/test/scopejumpdemo2.6502 new file mode 100644 index 0000000..a22564c --- /dev/null +++ b/test/scopejumpdemo2.6502 @@ -0,0 +1,19 @@ +ORG &2000 + +.start + + LDA #65:STA &70 + JSR foo + STY &71 + JSR bar + + \ JMP ldy_3_and_rts \ this won't assemble, even though it exists inside + \ scopejumpdemo2foo.6502 + LDY #3 + RTS + +INCLUDE "scopejumpdemo2.inc.6502" + +.end + +SAVE "test", start, end diff --git a/test/scopejumpdemo2.inc.6502 b/test/scopejumpdemo2.inc.6502 new file mode 100644 index 0000000..e07789e --- /dev/null +++ b/test/scopejumpdemo2.inc.6502 @@ -0,0 +1,49 @@ +\ This file is intended to be INCLUDEd by scopejumpdemo2.6502. +\ Its entire contents are wrapped in a scope, so only labels we explicitly +\ make global are available to the rest of the program. +{ + + \ foo is a global label; this is a subroutine we are making available to + \ the rest of the program outside this file. + \ + \ foo checks the contents of &70; if it's less than 42 we set X to 4, + \ otherwise we set X to 2. Either way, we set Y to 3. + .*foo + { + LDA &70 + CMP #42 + BCC lt_42 + LDX #2 + JMP ldy_3_and_rts + .lt_42 + LDX #4 + .^ldy_3_and_rts + LDY #3 + RTS + } + + \ bar is a global label; this is another subroutine we are making + \ available to the rest of the program. + \ + \ bar checks the contents of &71; if it's less than 42 we set X to 9, + \ otherwise we set X to 7. Either way, we set Y to 3. + .*bar + { + \ Because we're part of the same "module", we happen to know that + \ the LDY #3:RTS code is available inside foo, so we can take + \ advantage of that and not duplicate it here. This is an + \ implementation detail that might change, so we don't want that + \ exposing outside this file. Because we declared it ".^ldy_3_and_rts" + \ inside the scope enclosing foo's body, it's visible in the scope + \ which encloses this entire file, but not any parent scope. + LDA &71 + CMP #42 + BCC lt_42 + LDX #7 + JMP ldy_3_and_rts + .lt_42 \ local label, so doesn't clash with foo's lt_42 + LDX #9 + JMP ldy_3_and_rts + } + +} diff --git a/test/stringfunctions.6502 b/test/stringfunctions.6502 new file mode 100644 index 0000000..b08eca0 --- /dev/null +++ b/test/stringfunctions.6502 @@ -0,0 +1,26 @@ +org &2000 +.start + rts +.end + +save "test", start, end + +foo="Foo" +bar="Bar" +Foo="fooled!" +ASSERT foo + " " + bar = "Foo Bar" +ASSERT MID$(bar, 2, 1) = "a" +ASSERT UPPER$(foo) = "FOO" +ASSERT LOWER$(bar) = "bar" +ASSERT VAL("7.2") = 7.2 +ASSERT EVAL(foo) = "fooled!" +ASSERT STR$(7.2) = "7.2" +ASSERT STR$~(27.3) = "1B" +ASSERT LEN("") = 0 +ASSERT LEN(foo) = 3 +ASSERT LEN("a""b") = 3 +ASSERT CHR$(65) = "A" +ASSERT ASC(foo) = 70 +ASSERT ASC(MID$("a""b", 2, 1)) = '"' +ASSERT STRING$(3, bar) = bar + bar + bar +ASSERT TIME$ = TIME$("%a,%d %b %Y.%H:%M:%S") diff --git a/test/testrunner.py b/test/testrunner.py new file mode 100644 index 0000000..53775ad --- /dev/null +++ b/test/testrunner.py @@ -0,0 +1,142 @@ +# ===================================================================================================== +# +# Copyright (C) Charles Reilly 2021 +# +# This file is part of BeebAsm. +# +# BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with BeebAsm, as +# COPYING.txt. If not, see <http://www.gnu.org/licenses/>. +# +# ===================================================================================================== + +import os +import sys +import subprocess + +class TestFailure(Exception): + '''A test failed''' + pass + +def replace_extension(file_name, ext): + return os.path.splitext(file_name)[0] + ext + +def compare_files(name1, name2): + with open(name1, 'rb') as file1, open(name2, 'rb') as file2: + return file1.read() == file2.read() + +# Parse a string containing parameters separated by spaces. Parameters +# may be quoted with double quotes. +def parse_quoted_string(text): + params = [] + index = 0 + length = len(text) + while index < length: + while index < length and text[index].isspace(): + index += 1 + if index < length and text[index] == '\"': + index += 1 + start_index = index + while index < length and text[index] != '\"': + index += 1 + end_index = index + if index < length: + index += 1 + else: + start_index = index + while index < length and not text[index].isspace(): + index += 1 + end_index = index + if start_index != end_index: + params += [text[start_index:end_index]] + return params + +# Read switches from the first line of a beebasm source file, looking for: +# \ beebasm <switches> +def read_beebasm_switches(file_name): + with open(file_name) as test_file: + first_line = test_file.readline() + test_file.close() + if first_line == '' or first_line[0] != '\\': + return [] + # readline can return a trailing newline + if first_line[-1:] == '\n': + first_line = first_line[:-1] + params = parse_quoted_string(first_line[1:]) + if params == [] or params[0] != 'beebasm': + return [] + return params[1:] + +def beebasm_args(beebasm, file_name, ssd_name): + args = [beebasm] + read_beebasm_switches(file_name) + if ssd_name != None: + args += ['-do', ssd_name] + args += ['-v', '-i', file_name] + return args + +def execute_test(beebasm_arg_list): + print(beebasm_arg_list) + sys.stdout.flush() + return subprocess.Popen(beebasm_arg_list).wait() == 0 + +def run_test(beebasm, path, file_names, file_name): + if file_name.endswith('.inc.6502'): + return + + full_name = os.path.join(path, file_name) + print('='*70) + print('TEST: ' + full_name) + print('='*70) + + failure_test = file_name.endswith('.fail.6502') + gold_ssd = replace_extension(file_name, '.gold.ssd') + ssd_name = None + if gold_ssd in file_names: + ssd_name = replace_extension(file_name, '.ssd') + + result = execute_test(beebasm_args(beebasm, file_name, ssd_name)) + if failure_test and result: + raise TestFailure('Failure test succeeded: ' + full_name) + elif not failure_test and not result: + raise TestFailure('Success test failed: ' + full_name) + + if not failure_test and ssd_name != None and not compare_files(gold_ssd, ssd_name): + raise TestFailure('ssd does not match gold ssd :' + gold_ssd) + +def scan_directory(beebasm): + for (path, directory_names, file_names) in os.walk('.', topdown = True): + # Sort directory names; this allows simpler tests to be prioritised + directory_names.sort() + file_names.sort() + cwd = os.getcwd() + os.chdir(path) + for file_name in file_names: + if os.path.splitext(file_name)[1] == '.6502': + run_test(beebasm, path, file_names, file_name) + os.chdir(cwd) + + +if os.name == 'nt': + beebasm = 'beebasm.exe' +else: + beebasm = 'beebasm' +beebasm = os.path.join(os.getcwd(), beebasm) + +os.chdir('test') + +try: + scan_directory(beebasm) + print("SUCCESS") + sys.exit(0) + +except TestFailure as e: + print("FAILURE: " + e.args[0]) + sys.exit(1) + From 79bdd120a03ac523c121358f189db2578e2bbecf Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 6 Jul 2021 21:41:30 +0100 Subject: [PATCH 064/144] Changed testrunner to unix line endings --- test/testrunner.py | 284 ++++++++++++++++++++++----------------------- 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/test/testrunner.py b/test/testrunner.py index 53775ad..64219a8 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -1,142 +1,142 @@ -# ===================================================================================================== -# -# Copyright (C) Charles Reilly 2021 -# -# This file is part of BeebAsm. -# -# BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with BeebAsm, as -# COPYING.txt. If not, see <http://www.gnu.org/licenses/>. -# -# ===================================================================================================== - -import os -import sys -import subprocess - -class TestFailure(Exception): - '''A test failed''' - pass - -def replace_extension(file_name, ext): - return os.path.splitext(file_name)[0] + ext - -def compare_files(name1, name2): - with open(name1, 'rb') as file1, open(name2, 'rb') as file2: - return file1.read() == file2.read() - -# Parse a string containing parameters separated by spaces. Parameters -# may be quoted with double quotes. -def parse_quoted_string(text): - params = [] - index = 0 - length = len(text) - while index < length: - while index < length and text[index].isspace(): - index += 1 - if index < length and text[index] == '\"': - index += 1 - start_index = index - while index < length and text[index] != '\"': - index += 1 - end_index = index - if index < length: - index += 1 - else: - start_index = index - while index < length and not text[index].isspace(): - index += 1 - end_index = index - if start_index != end_index: - params += [text[start_index:end_index]] - return params - -# Read switches from the first line of a beebasm source file, looking for: -# \ beebasm <switches> -def read_beebasm_switches(file_name): - with open(file_name) as test_file: - first_line = test_file.readline() - test_file.close() - if first_line == '' or first_line[0] != '\\': - return [] - # readline can return a trailing newline - if first_line[-1:] == '\n': - first_line = first_line[:-1] - params = parse_quoted_string(first_line[1:]) - if params == [] or params[0] != 'beebasm': - return [] - return params[1:] - -def beebasm_args(beebasm, file_name, ssd_name): - args = [beebasm] + read_beebasm_switches(file_name) - if ssd_name != None: - args += ['-do', ssd_name] - args += ['-v', '-i', file_name] - return args - -def execute_test(beebasm_arg_list): - print(beebasm_arg_list) - sys.stdout.flush() - return subprocess.Popen(beebasm_arg_list).wait() == 0 - -def run_test(beebasm, path, file_names, file_name): - if file_name.endswith('.inc.6502'): - return - - full_name = os.path.join(path, file_name) - print('='*70) - print('TEST: ' + full_name) - print('='*70) - - failure_test = file_name.endswith('.fail.6502') - gold_ssd = replace_extension(file_name, '.gold.ssd') - ssd_name = None - if gold_ssd in file_names: - ssd_name = replace_extension(file_name, '.ssd') - - result = execute_test(beebasm_args(beebasm, file_name, ssd_name)) - if failure_test and result: - raise TestFailure('Failure test succeeded: ' + full_name) - elif not failure_test and not result: - raise TestFailure('Success test failed: ' + full_name) - - if not failure_test and ssd_name != None and not compare_files(gold_ssd, ssd_name): - raise TestFailure('ssd does not match gold ssd :' + gold_ssd) - -def scan_directory(beebasm): - for (path, directory_names, file_names) in os.walk('.', topdown = True): - # Sort directory names; this allows simpler tests to be prioritised - directory_names.sort() - file_names.sort() - cwd = os.getcwd() - os.chdir(path) - for file_name in file_names: - if os.path.splitext(file_name)[1] == '.6502': - run_test(beebasm, path, file_names, file_name) - os.chdir(cwd) - - -if os.name == 'nt': - beebasm = 'beebasm.exe' -else: - beebasm = 'beebasm' -beebasm = os.path.join(os.getcwd(), beebasm) - -os.chdir('test') - -try: - scan_directory(beebasm) - print("SUCCESS") - sys.exit(0) - -except TestFailure as e: - print("FAILURE: " + e.args[0]) - sys.exit(1) - +# ===================================================================================================== +# +# Copyright (C) Charles Reilly 2021 +# +# This file is part of BeebAsm. +# +# BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with BeebAsm, as +# COPYING.txt. If not, see <http://www.gnu.org/licenses/>. +# +# ===================================================================================================== + +import os +import sys +import subprocess + +class TestFailure(Exception): + '''A test failed''' + pass + +def replace_extension(file_name, ext): + return os.path.splitext(file_name)[0] + ext + +def compare_files(name1, name2): + with open(name1, 'rb') as file1, open(name2, 'rb') as file2: + return file1.read() == file2.read() + +# Parse a string containing parameters separated by spaces. Parameters +# may be quoted with double quotes. +def parse_quoted_string(text): + params = [] + index = 0 + length = len(text) + while index < length: + while index < length and text[index].isspace(): + index += 1 + if index < length and text[index] == '\"': + index += 1 + start_index = index + while index < length and text[index] != '\"': + index += 1 + end_index = index + if index < length: + index += 1 + else: + start_index = index + while index < length and not text[index].isspace(): + index += 1 + end_index = index + if start_index != end_index: + params += [text[start_index:end_index]] + return params + +# Read switches from the first line of a beebasm source file, looking for: +# \ beebasm <switches> +def read_beebasm_switches(file_name): + with open(file_name) as test_file: + first_line = test_file.readline() + test_file.close() + if first_line == '' or first_line[0] != '\\': + return [] + # readline can return a trailing newline + if first_line[-1:] == '\n': + first_line = first_line[:-1] + params = parse_quoted_string(first_line[1:]) + if params == [] or params[0] != 'beebasm': + return [] + return params[1:] + +def beebasm_args(beebasm, file_name, ssd_name): + args = [beebasm] + read_beebasm_switches(file_name) + if ssd_name != None: + args += ['-do', ssd_name] + args += ['-v', '-i', file_name] + return args + +def execute_test(beebasm_arg_list): + print(beebasm_arg_list) + sys.stdout.flush() + return subprocess.Popen(beebasm_arg_list).wait() == 0 + +def run_test(beebasm, path, file_names, file_name): + if file_name.endswith('.inc.6502'): + return + + full_name = os.path.join(path, file_name) + print('='*70) + print('TEST: ' + full_name) + print('='*70) + + failure_test = file_name.endswith('.fail.6502') + gold_ssd = replace_extension(file_name, '.gold.ssd') + ssd_name = None + if gold_ssd in file_names: + ssd_name = replace_extension(file_name, '.ssd') + + result = execute_test(beebasm_args(beebasm, file_name, ssd_name)) + if failure_test and result: + raise TestFailure('Failure test succeeded: ' + full_name) + elif not failure_test and not result: + raise TestFailure('Success test failed: ' + full_name) + + if not failure_test and ssd_name != None and not compare_files(gold_ssd, ssd_name): + raise TestFailure('ssd does not match gold ssd :' + gold_ssd) + +def scan_directory(beebasm): + for (path, directory_names, file_names) in os.walk('.', topdown = True): + # Sort directory names; this allows simpler tests to be prioritised + directory_names.sort() + file_names.sort() + cwd = os.getcwd() + os.chdir(path) + for file_name in file_names: + if os.path.splitext(file_name)[1] == '.6502': + run_test(beebasm, path, file_names, file_name) + os.chdir(cwd) + + +if os.name == 'nt': + beebasm = 'beebasm.exe' +else: + beebasm = 'beebasm' +beebasm = os.path.join(os.getcwd(), beebasm) + +os.chdir('test') + +try: + scan_directory(beebasm) + print("SUCCESS") + sys.exit(0) + +except TestFailure as e: + print("FAILURE: " + e.args[0]) + sys.exit(1) + From fb1c6c29beeb53979a46cec3805b8093577a7865 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 6 Jul 2021 21:42:18 +0100 Subject: [PATCH 065/144] GitHub action: automatically run test after build --- .github/workflows/actions.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 08badd6..b0049fa 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -18,6 +18,7 @@ jobs: - run: cmake . - run: make - run: make test + - run: python3 test/testrunner.py make: runs-on: ${{ matrix.os }} strategy: @@ -27,3 +28,4 @@ jobs: steps: - uses: actions/checkout@v2 - run: make -C src all + - run: python3 test/testrunner.py From 1eb8d8ac3f637cd6ab327f65381a3fa7e3653112 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 6 Jul 2021 22:47:19 +0100 Subject: [PATCH 066/144] Run tests from make or cmake --- .github/workflows/actions.yml | 2 -- CMakeLists.txt | 1 + src/Makefile | 3 ++- src/Makefile.inc | 7 ++++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index b0049fa..08badd6 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -18,7 +18,6 @@ jobs: - run: cmake . - run: make - run: make test - - run: python3 test/testrunner.py make: runs-on: ${{ matrix.os }} strategy: @@ -28,4 +27,3 @@ jobs: steps: - uses: actions/checkout@v2 - run: make -C src all - - run: python3 test/testrunner.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7906ba6..409349f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,3 +19,4 @@ install(FILES ${CMAKE_SOURCE_DIR}/beebasm.1 DESTINATION share/man/man1) enable_testing() add_test(NAME Runs COMMAND ./beebasm -i demo.6502 -do demo.ssd -boot Code -v) +add_test(NAME Tests COMMAND python3 test/testrunner.py) diff --git a/src/Makefile b/src/Makefile index cf471ec..324809b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -53,7 +53,8 @@ LDLIBS := -lm PARAMS := -i ../demo.6502 -do ../demo.ssd -boot Code -v - +# Command to run the tests +TEST := cd .. && python3 test/testrunner.py #-------------------------------------------------------------------------------------------------- diff --git a/src/Makefile.inc b/src/Makefile.inc index 026d772..67fafe9 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -168,6 +168,7 @@ help: $(ECHO) make all .... Build and run code $(ECHO) make code ... Build code $(ECHO) make run .... Run code + $(ECHO) make test ... Run tests $(ECHO) make clean .. Clean code $(ECHO) make help ... Display this message again $(ECHO) @@ -192,7 +193,11 @@ run: $(VB)$(TARGET) $(PARAMS) -all: code run +test: + $(VB)$(TEST) + + +all: code run test clean: From 096c368c08c07625cbe5cb5bd95f76b36124a926 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 6 Jul 2021 23:13:16 +0100 Subject: [PATCH 067/144] Redirect test stderr to stdout to avoid interleaving issues --- test/testrunner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/testrunner.py b/test/testrunner.py index 64219a8..2a4d6fd 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -84,7 +84,8 @@ def beebasm_args(beebasm, file_name, ssd_name): def execute_test(beebasm_arg_list): print(beebasm_arg_list) sys.stdout.flush() - return subprocess.Popen(beebasm_arg_list).wait() == 0 + # Child stderr written to stdout to avoid output interleaving problems + return subprocess.Popen(beebasm_arg_list, stderr = sys.stdout).wait() == 0 def run_test(beebasm, path, file_names, file_name): if file_name.endswith('.inc.6502'): From 1cecb135617ef0c9a92e98332c7db66f97abacf8 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 7 Jul 2021 21:05:51 +0100 Subject: [PATCH 068/144] Rename output ssds to test.ssd and add to .gitignore --- .gitignore | 2 ++ test/invalidbasic1.fail.6502 | 2 +- test/invalidbasic2.fail.6502 | 2 +- test/putbasicnonexistentdemo.fail.6502 | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cbf1c13..d57abdd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.swp /beebasm /src/objects +test/**/test +test/**/test.ssd diff --git a/test/invalidbasic1.fail.6502 b/test/invalidbasic1.fail.6502 index e879ed6..4764ff4 100644 --- a/test/invalidbasic1.fail.6502 +++ b/test/invalidbasic1.fail.6502 @@ -1,4 +1,4 @@ -\ beebasm -do invalidbasic1.ssd +\ beebasm -do test.ssd org &2000 .start diff --git a/test/invalidbasic2.fail.6502 b/test/invalidbasic2.fail.6502 index 34665e3..bc8b095 100644 --- a/test/invalidbasic2.fail.6502 +++ b/test/invalidbasic2.fail.6502 @@ -1,4 +1,4 @@ -\ beebasm -do invalidbasic2.ssd +\ beebasm -do test.ssd org &2000 .start diff --git a/test/putbasicnonexistentdemo.fail.6502 b/test/putbasicnonexistentdemo.fail.6502 index 0e3ca3d..5c168e4 100644 --- a/test/putbasicnonexistentdemo.fail.6502 +++ b/test/putbasicnonexistentdemo.fail.6502 @@ -1,4 +1,4 @@ -\ beebasm -do putbasicnonexistentdemo.ssd +\ beebasm -do test.ssd org &2000 .start From e5a515efbe6642b4e0987576458fda937ba16f02 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 7 Jul 2021 21:45:45 +0100 Subject: [PATCH 069/144] Make testing not verbose by default Output is written to test/testlog.txt by default. -v option writes output to stdout instead. Update Makefile to add -v if VERBOSE is set. Updated github action to set VERBOSE so test output is visible in the log. --- .github/workflows/actions.yml | 2 +- .gitignore | 1 + src/Makefile | 2 ++ src/Makefile.inc | 4 +++- test/testrunner.py | 32 +++++++++++++++++++++++++++++--- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 08badd6..d1a2a25 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -26,4 +26,4 @@ jobs: name: Compile via Makefile on ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - run: make -C src all + - run: make -C src all VERBOSE=1 diff --git a/.gitignore b/.gitignore index d57abdd..cac9518 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ *.swp /beebasm /src/objects +test/testlog.txt test/**/test test/**/test.ssd diff --git a/src/Makefile b/src/Makefile index 324809b..4f31093 100644 --- a/src/Makefile +++ b/src/Makefile @@ -54,8 +54,10 @@ LDLIBS := -lm PARAMS := -i ../demo.6502 -do ../demo.ssd -boot Code -v # Command to run the tests + TEST := cd .. && python3 test/testrunner.py + #-------------------------------------------------------------------------------------------------- include Makefile.inc diff --git a/src/Makefile.inc b/src/Makefile.inc index 67fafe9..baf62c1 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -145,9 +145,11 @@ CPUS ?= 1 ifdef VERBOSE VB := VB_MAKE := +VB_TEST := -v else VB := @@ VB_MAKE := -s +VB_TEST := endif @@ -194,7 +196,7 @@ run: test: - $(VB)$(TEST) + $(VB)$(TEST) $(VB_TEST) all: code run test diff --git a/test/testrunner.py b/test/testrunner.py index 2a4d6fd..b65cb89 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -85,7 +85,7 @@ def execute_test(beebasm_arg_list): print(beebasm_arg_list) sys.stdout.flush() # Child stderr written to stdout to avoid output interleaving problems - return subprocess.Popen(beebasm_arg_list, stderr = sys.stdout).wait() == 0 + return subprocess.Popen(beebasm_arg_list, stdout = sys.stdout, stderr = sys.stdout).wait() == 0 def run_test(beebasm, path, file_names, file_name): if file_name.endswith('.inc.6502'): @@ -123,6 +123,28 @@ def scan_directory(beebasm): run_test(beebasm, path, file_names, file_name) os.chdir(cwd) +def parse_args(): + global verbose + + if len(sys.argv) == 1: + return True + + if len(sys.argv) > 2: + return False + + if sys.argv[1] == '-v': + verbose = True + else: + return False + + return True + + +verbose = False + +if not parse_args(): + print("testrunner.py [-v]") + sys.exit(2) if os.name == 'nt': beebasm = 'beebasm.exe' @@ -132,12 +154,16 @@ def scan_directory(beebasm): os.chdir('test') +original_stdout = sys.stdout +if not verbose: + sys.stdout = open('testlog.txt', mode = 'w', encoding = sys.stdout.encoding) + try: scan_directory(beebasm) - print("SUCCESS") + print("SUCCESS: beebasm tests succeeded", file = original_stdout) sys.exit(0) except TestFailure as e: - print("FAILURE: " + e.args[0]) + print("FAILURE: " + e.args[0], file = original_stdout) sys.exit(1) From acc184986eba636cd20041fd5c379d4af04f3032 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 7 Jul 2021 21:56:02 +0100 Subject: [PATCH 070/144] Rename ssds produced for gold testing to test.ssd --- test/README.md | 4 ++-- test/testrunner.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/README.md b/test/README.md index d444b27..ee0cc02 100644 --- a/test/README.md +++ b/test/README.md @@ -29,7 +29,7 @@ known-good output from running the test. The test runner will add the `-do` option to the command-line. For success tests, if will also check that the `.ssd` produced by the test is identical to the gold ssd. -For example, if a directory contains `test.6502` and `test.gold.ssd` then +For example, if a directory contains `sometest.6502` and `sometest.gold.ssd` then the test will be required to produce a `test.ssd` file that is identical -to `test.gold.ssd`. +to `sometest.gold.ssd`. Note that the output file is always called `test.ssd`. diff --git a/test/testrunner.py b/test/testrunner.py index b65cb89..7470535 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -100,7 +100,7 @@ def run_test(beebasm, path, file_names, file_name): gold_ssd = replace_extension(file_name, '.gold.ssd') ssd_name = None if gold_ssd in file_names: - ssd_name = replace_extension(file_name, '.ssd') + ssd_name = 'test.ssd' result = execute_test(beebasm_args(beebasm, file_name, ssd_name)) if failure_test and result: @@ -109,7 +109,7 @@ def run_test(beebasm, path, file_names, file_name): raise TestFailure('Success test failed: ' + full_name) if not failure_test and ssd_name != None and not compare_files(gold_ssd, ssd_name): - raise TestFailure('ssd does not match gold ssd :' + gold_ssd) + raise TestFailure('ssd does not match gold ssd: ' + gold_ssd) def scan_directory(beebasm): for (path, directory_names, file_names) in os.walk('.', topdown = True): From 8f1e676f97ea0b154704d9893c0be515331a11bc Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 7 Jul 2021 23:40:09 +0100 Subject: [PATCH 071/144] Compare beebasm printed output to .gold.txt files --- .gitignore | 1 + test/errorlinenumber1.fail.gold.txt | 7 +++++ test/errorlinenumber2.fail.gold.txt | 4 +++ test/testrunner.py | 45 +++++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 test/errorlinenumber1.fail.gold.txt create mode 100644 test/errorlinenumber2.fail.gold.txt diff --git a/.gitignore b/.gitignore index cac9518..18c63d7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ test/testlog.txt test/**/test test/**/test.ssd +test/**/testgold.txt diff --git a/test/errorlinenumber1.fail.gold.txt b/test/errorlinenumber1.fail.gold.txt new file mode 100644 index 0000000..5f9dcc8 --- /dev/null +++ b/test/errorlinenumber1.fail.gold.txt @@ -0,0 +1,7 @@ +errorlinenumber1.fail.6502:10: error: Indirect mode not allowed for this instruction. + +ldx (&70),y + ^ + +Call stack: +errorlinenumber1.fail.6502:13 diff --git a/test/errorlinenumber2.fail.gold.txt b/test/errorlinenumber2.fail.gold.txt new file mode 100644 index 0000000..f60f45c --- /dev/null +++ b/test/errorlinenumber2.fail.gold.txt @@ -0,0 +1,4 @@ +errorlinenumber2.fail.6502:9: error: Indirect mode not allowed for this instruction. + +ldx (&70),y + ^ diff --git a/test/testrunner.py b/test/testrunner.py index 7470535..7ede6f3 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -81,11 +81,19 @@ def beebasm_args(beebasm, file_name, ssd_name): args += ['-v', '-i', file_name] return args -def execute_test(beebasm_arg_list): +def execute(args, output): + # Child stderr written to stdout to avoid output interleaving problems + return subprocess.Popen(args, stdout = output, stderr = output).wait() == 0 + +def execute_test(beebasm_arg_list, capture_name): print(beebasm_arg_list) sys.stdout.flush() - # Child stderr written to stdout to avoid output interleaving problems - return subprocess.Popen(beebasm_arg_list, stdout = sys.stdout, stderr = sys.stdout).wait() == 0 + + if capture_name == None: + return execute(beebasm_arg_list, sys.stdout) + + with open(capture_name, 'w', encoding = sys.stdout.encoding) as capture: + return execute(beebasm_arg_list, capture) def run_test(beebasm, path, file_names, file_name): if file_name.endswith('.inc.6502'): @@ -98,18 +106,43 @@ def run_test(beebasm, path, file_names, file_name): failure_test = file_name.endswith('.fail.6502') gold_ssd = replace_extension(file_name, '.gold.ssd') + gold_txt = replace_extension(file_name, '.gold.txt') ssd_name = None + gold_capture = None if gold_ssd in file_names: ssd_name = 'test.ssd' + if gold_txt in file_names: + gold_capture = 'testgold.txt' + + result = execute_test(beebasm_args(beebasm, file_name, ssd_name), gold_capture) + + if not gold_capture is None: + # This won't work well if a test produces gigabytes of output. Don't do that! + with open(gold_capture, 'r') as capture_file: + capture = capture_file.read() + # Duplicate captured data on stdout + sys.stdout.write(capture) + with open(gold_txt, 'r') as gold_file: + gold = gold_file.read() + print('Comparing beebasm output to', gold_txt, end = '') + if capture.find(gold) != -1: + print(' succeeded') + else: + print(' failed') + raise TestFailure('Test output does not include gold text: ' + gold_txt) - result = execute_test(beebasm_args(beebasm, file_name, ssd_name)) if failure_test and result: raise TestFailure('Failure test succeeded: ' + full_name) elif not failure_test and not result: raise TestFailure('Success test failed: ' + full_name) - if not failure_test and ssd_name != None and not compare_files(gold_ssd, ssd_name): - raise TestFailure('ssd does not match gold ssd: ' + gold_ssd) + if not failure_test and ssd_name != None: + print('Comparing output ssd to', gold_ssd, end = '') + if compare_files(gold_ssd, ssd_name): + print(' succeeded') + else: + print(' failed') + raise TestFailure('ssd does not match gold ssd: ' + gold_ssd) def scan_directory(beebasm): for (path, directory_names, file_names) in os.walk('.', topdown = True): From 0112a6addfb2f8053ab81c88352e89004dae3383 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 8 Jul 2021 16:13:21 +0100 Subject: [PATCH 072/144] Rearranged tests into rough priority order --- test/{ => 2-expressions}/stringfunctions.6502 | 0 test/{ => 3-directives}/autolinenumdemo.6502 | 0 test/{ => 3-directives}/autolinenumdemo.bas | 0 test/{ => 3-directives}/basicrhstoken.6502 | 0 test/{ => 3-directives}/basicrhstoken.bas | 0 test/{ => 3-directives}/invalidbasic1.bas | 0 test/{ => 3-directives}/invalidbasic1.fail.6502 | 0 test/{ => 3-directives}/invalidbasic2.bas | 0 test/{ => 3-directives}/invalidbasic2.fail.6502 | 0 test/{ => 3-directives}/putbasicnonexistentdemo.fail.6502 | 0 test/{ => 3-directives}/putfilenonexistentdemo.fail.6502 | 0 test/{ => 4-assembler}/local-forward-branch-1.6502 | 0 test/{ => 4-assembler}/local-forward-branch-2.6502 | 0 test/{ => 4-assembler}/local-forward-branch-3.6502 | 0 test/{ => 4-assembler}/local-forward-branch-4.6502 | 0 test/{ => 4-assembler}/local-forward-branch-5.6502 | 0 test/{ => 4-assembler}/scopejumpdemo1.6502 | 0 test/{ => 4-assembler}/scopejumpdemo2.6502 | 0 test/{ => 4-assembler}/scopejumpdemo2.inc.6502 | 0 test/{ => 5-errors}/assertdemo.fail.6502 | 0 test/{ => 5-errors}/callstackdemo.fail.6502 | 0 test/{ => 5-errors}/errorlinenumber1.fail.6502 | 0 test/{ => 5-errors}/errorlinenumber1.fail.gold.txt | 0 test/{ => 5-errors}/errorlinenumber2.fail.6502 | 0 test/{ => 5-errors}/errorlinenumber2.fail.gold.txt | 0 test/{ => 5-errors}/filelinecallstackdemo.6502 | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename test/{ => 2-expressions}/stringfunctions.6502 (100%) rename test/{ => 3-directives}/autolinenumdemo.6502 (100%) rename test/{ => 3-directives}/autolinenumdemo.bas (100%) rename test/{ => 3-directives}/basicrhstoken.6502 (100%) rename test/{ => 3-directives}/basicrhstoken.bas (100%) rename test/{ => 3-directives}/invalidbasic1.bas (100%) rename test/{ => 3-directives}/invalidbasic1.fail.6502 (100%) rename test/{ => 3-directives}/invalidbasic2.bas (100%) rename test/{ => 3-directives}/invalidbasic2.fail.6502 (100%) rename test/{ => 3-directives}/putbasicnonexistentdemo.fail.6502 (100%) rename test/{ => 3-directives}/putfilenonexistentdemo.fail.6502 (100%) rename test/{ => 4-assembler}/local-forward-branch-1.6502 (100%) rename test/{ => 4-assembler}/local-forward-branch-2.6502 (100%) rename test/{ => 4-assembler}/local-forward-branch-3.6502 (100%) rename test/{ => 4-assembler}/local-forward-branch-4.6502 (100%) rename test/{ => 4-assembler}/local-forward-branch-5.6502 (100%) rename test/{ => 4-assembler}/scopejumpdemo1.6502 (100%) rename test/{ => 4-assembler}/scopejumpdemo2.6502 (100%) rename test/{ => 4-assembler}/scopejumpdemo2.inc.6502 (100%) rename test/{ => 5-errors}/assertdemo.fail.6502 (100%) rename test/{ => 5-errors}/callstackdemo.fail.6502 (100%) rename test/{ => 5-errors}/errorlinenumber1.fail.6502 (100%) rename test/{ => 5-errors}/errorlinenumber1.fail.gold.txt (100%) rename test/{ => 5-errors}/errorlinenumber2.fail.6502 (100%) rename test/{ => 5-errors}/errorlinenumber2.fail.gold.txt (100%) rename test/{ => 5-errors}/filelinecallstackdemo.6502 (100%) diff --git a/test/stringfunctions.6502 b/test/2-expressions/stringfunctions.6502 similarity index 100% rename from test/stringfunctions.6502 rename to test/2-expressions/stringfunctions.6502 diff --git a/test/autolinenumdemo.6502 b/test/3-directives/autolinenumdemo.6502 similarity index 100% rename from test/autolinenumdemo.6502 rename to test/3-directives/autolinenumdemo.6502 diff --git a/test/autolinenumdemo.bas b/test/3-directives/autolinenumdemo.bas similarity index 100% rename from test/autolinenumdemo.bas rename to test/3-directives/autolinenumdemo.bas diff --git a/test/basicrhstoken.6502 b/test/3-directives/basicrhstoken.6502 similarity index 100% rename from test/basicrhstoken.6502 rename to test/3-directives/basicrhstoken.6502 diff --git a/test/basicrhstoken.bas b/test/3-directives/basicrhstoken.bas similarity index 100% rename from test/basicrhstoken.bas rename to test/3-directives/basicrhstoken.bas diff --git a/test/invalidbasic1.bas b/test/3-directives/invalidbasic1.bas similarity index 100% rename from test/invalidbasic1.bas rename to test/3-directives/invalidbasic1.bas diff --git a/test/invalidbasic1.fail.6502 b/test/3-directives/invalidbasic1.fail.6502 similarity index 100% rename from test/invalidbasic1.fail.6502 rename to test/3-directives/invalidbasic1.fail.6502 diff --git a/test/invalidbasic2.bas b/test/3-directives/invalidbasic2.bas similarity index 100% rename from test/invalidbasic2.bas rename to test/3-directives/invalidbasic2.bas diff --git a/test/invalidbasic2.fail.6502 b/test/3-directives/invalidbasic2.fail.6502 similarity index 100% rename from test/invalidbasic2.fail.6502 rename to test/3-directives/invalidbasic2.fail.6502 diff --git a/test/putbasicnonexistentdemo.fail.6502 b/test/3-directives/putbasicnonexistentdemo.fail.6502 similarity index 100% rename from test/putbasicnonexistentdemo.fail.6502 rename to test/3-directives/putbasicnonexistentdemo.fail.6502 diff --git a/test/putfilenonexistentdemo.fail.6502 b/test/3-directives/putfilenonexistentdemo.fail.6502 similarity index 100% rename from test/putfilenonexistentdemo.fail.6502 rename to test/3-directives/putfilenonexistentdemo.fail.6502 diff --git a/test/local-forward-branch-1.6502 b/test/4-assembler/local-forward-branch-1.6502 similarity index 100% rename from test/local-forward-branch-1.6502 rename to test/4-assembler/local-forward-branch-1.6502 diff --git a/test/local-forward-branch-2.6502 b/test/4-assembler/local-forward-branch-2.6502 similarity index 100% rename from test/local-forward-branch-2.6502 rename to test/4-assembler/local-forward-branch-2.6502 diff --git a/test/local-forward-branch-3.6502 b/test/4-assembler/local-forward-branch-3.6502 similarity index 100% rename from test/local-forward-branch-3.6502 rename to test/4-assembler/local-forward-branch-3.6502 diff --git a/test/local-forward-branch-4.6502 b/test/4-assembler/local-forward-branch-4.6502 similarity index 100% rename from test/local-forward-branch-4.6502 rename to test/4-assembler/local-forward-branch-4.6502 diff --git a/test/local-forward-branch-5.6502 b/test/4-assembler/local-forward-branch-5.6502 similarity index 100% rename from test/local-forward-branch-5.6502 rename to test/4-assembler/local-forward-branch-5.6502 diff --git a/test/scopejumpdemo1.6502 b/test/4-assembler/scopejumpdemo1.6502 similarity index 100% rename from test/scopejumpdemo1.6502 rename to test/4-assembler/scopejumpdemo1.6502 diff --git a/test/scopejumpdemo2.6502 b/test/4-assembler/scopejumpdemo2.6502 similarity index 100% rename from test/scopejumpdemo2.6502 rename to test/4-assembler/scopejumpdemo2.6502 diff --git a/test/scopejumpdemo2.inc.6502 b/test/4-assembler/scopejumpdemo2.inc.6502 similarity index 100% rename from test/scopejumpdemo2.inc.6502 rename to test/4-assembler/scopejumpdemo2.inc.6502 diff --git a/test/assertdemo.fail.6502 b/test/5-errors/assertdemo.fail.6502 similarity index 100% rename from test/assertdemo.fail.6502 rename to test/5-errors/assertdemo.fail.6502 diff --git a/test/callstackdemo.fail.6502 b/test/5-errors/callstackdemo.fail.6502 similarity index 100% rename from test/callstackdemo.fail.6502 rename to test/5-errors/callstackdemo.fail.6502 diff --git a/test/errorlinenumber1.fail.6502 b/test/5-errors/errorlinenumber1.fail.6502 similarity index 100% rename from test/errorlinenumber1.fail.6502 rename to test/5-errors/errorlinenumber1.fail.6502 diff --git a/test/errorlinenumber1.fail.gold.txt b/test/5-errors/errorlinenumber1.fail.gold.txt similarity index 100% rename from test/errorlinenumber1.fail.gold.txt rename to test/5-errors/errorlinenumber1.fail.gold.txt diff --git a/test/errorlinenumber2.fail.6502 b/test/5-errors/errorlinenumber2.fail.6502 similarity index 100% rename from test/errorlinenumber2.fail.6502 rename to test/5-errors/errorlinenumber2.fail.6502 diff --git a/test/errorlinenumber2.fail.gold.txt b/test/5-errors/errorlinenumber2.fail.gold.txt similarity index 100% rename from test/errorlinenumber2.fail.gold.txt rename to test/5-errors/errorlinenumber2.fail.gold.txt diff --git a/test/filelinecallstackdemo.6502 b/test/5-errors/filelinecallstackdemo.6502 similarity index 100% rename from test/filelinecallstackdemo.6502 rename to test/5-errors/filelinecallstackdemo.6502 From ba0b4ccb69233c999dba99516543fac5e1234fe2 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 8 Jul 2021 17:19:51 +0100 Subject: [PATCH 073/144] Simple tests for all the operators --- test/2-expressions/operators.6502 | 79 +++++++++++++++++++++++ test/2-expressions/operators1.fail.6502 | 2 + test/2-expressions/operators2.fail.6502 | 2 + test/3-directives/assertfalse.fail.6502 | 2 + test/5-errors/callstackdemo.fail.gold.txt | 8 +++ 5 files changed, 93 insertions(+) create mode 100644 test/2-expressions/operators.6502 create mode 100644 test/2-expressions/operators1.fail.6502 create mode 100644 test/2-expressions/operators2.fail.6502 create mode 100644 test/3-directives/assertfalse.fail.6502 create mode 100644 test/5-errors/callstackdemo.fail.gold.txt diff --git a/test/2-expressions/operators.6502 b/test/2-expressions/operators.6502 new file mode 100644 index 0000000..23c6102 --- /dev/null +++ b/test/2-expressions/operators.6502 @@ -0,0 +1,79 @@ +\ Comparisons +assert(1=1) +assert(1==1) +assert(1<>2) +assert(1!=2) +assert(1<=1) +assert(1<=2) +assert(1<2) +assert(1>=1) +assert(2>=1) +assert(2>1) + +\ Arithmetic +assert(2+3=5) +assert(2-3=-1) +assert(2*3=6) +assert(5/2=2.5) +assert(1+2*3=7) +assert((1+2)*3=9) +assert(2^3=8) +assert(9.9 DIV 2.9=4) +assert(9.9 MOD 2.9=1) + +\ Shifts +assert(3 << 5=96) +assert(96 >> 5 = 3) + +\ Logic +assert((14 AND 28)=12) +assert((14 OR 28)=30) +assert((14 EOR 28)=18) +assert(not(-1)=0) +assert(not(0)=-1) +assert(not(1)=-2) + +\ Bytes +assert(hi(&1234)=&12) +assert(>&1234=&12) +assert(lo(&1234)=&34) +assert(<&1234=&34) + +\ Maths +assert(sqr(64)=8) +assert(int(3.9)=3) +assert(int(-3.9)=-3) +assert(abs(2.5)=2.5) +assert(abs(-2.5)=2.5) +assert(sgn(0)=0) +assert(sgn(-56)=-1) +assert(sgn(56)=1) + +e=0.000000000001 +assert(sin(0)=0) +assert(sin(PI/2)=1) +for d, 1, 179, 2 + r=rad(d) + assert(abs(deg(r)-d) < e) + c=cos(r) + s=sin(r) + t=tan(r) + r2=r-PI/2 + s2=sin(r2) + t2=tan(r2) + assert(abs(acs(c)-r) < e) + assert(abs(asn(s2)-r2) < e) + assert(abs(atn(t2)-r2) < e) + assert(abs(c-sin(r+PI/2)) < e) + assert(abs(t-s/c) < e) +next + +log10=ln(10) +for d, 1, 20 + natural=ln(d) + base10=log(d) + p=exp(natural) + assert(abs(p-d) < e) + assert(abs(base10-natural/log10) < e) + assert(abs(10^base10-d) < e) +next diff --git a/test/2-expressions/operators1.fail.6502 b/test/2-expressions/operators1.fail.6502 new file mode 100644 index 0000000..dfbc723 --- /dev/null +++ b/test/2-expressions/operators1.fail.6502 @@ -0,0 +1,2 @@ +\ This should fail +assert(2>2) diff --git a/test/2-expressions/operators2.fail.6502 b/test/2-expressions/operators2.fail.6502 new file mode 100644 index 0000000..04658ef --- /dev/null +++ b/test/2-expressions/operators2.fail.6502 @@ -0,0 +1,2 @@ +\ This should fail +assert(2<2) diff --git a/test/3-directives/assertfalse.fail.6502 b/test/3-directives/assertfalse.fail.6502 new file mode 100644 index 0000000..e001fb8 --- /dev/null +++ b/test/3-directives/assertfalse.fail.6502 @@ -0,0 +1,2 @@ +\ This should fail +assert(0) diff --git a/test/5-errors/callstackdemo.fail.gold.txt b/test/5-errors/callstackdemo.fail.gold.txt new file mode 100644 index 0000000..7e33bde --- /dev/null +++ b/test/5-errors/callstackdemo.fail.gold.txt @@ -0,0 +1,8 @@ +callstackdemo.fail.6502:5: error: Immediate constants cannot be greater than 255. + +adc #n + ^ + +Call stack: +callstackdemo.fail.6502:10 +callstackdemo.fail.6502:16 From 07ea3be73757f5cb5260e8c4ecb2f12ceb6a4fe9 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 8 Jul 2021 19:00:41 +0100 Subject: [PATCH 074/144] cmake on github - show verbose test output when test fails --- .github/workflows/actions.yml | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index d1a2a25..1d996b2 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2 - run: cmake . - run: make - - run: make test + - run: make test CTEST_OUTPUT_ON_FAILURE=TRUE make: runs-on: ${{ matrix.os }} strategy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 409349f..0e2b006 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,4 +19,4 @@ install(FILES ${CMAKE_SOURCE_DIR}/beebasm.1 DESTINATION share/man/man1) enable_testing() add_test(NAME Runs COMMAND ./beebasm -i demo.6502 -do demo.ssd -boot Code -v) -add_test(NAME Tests COMMAND python3 test/testrunner.py) +add_test(NAME Tests COMMAND python3 test/testrunner.py -v) From c0235053c12b1ecb183c0466eaf48b4a0b2df894 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Fri, 9 Jul 2021 19:51:29 +0100 Subject: [PATCH 075/144] Delete pseudo-test cases from examples directory These were dumped in examples for want of anywhere better, but now we have a test harness which executes (other copies of) them, they can be removed - they aren't all that instructive as examples. diff --git a/examples/basicrhstoken.6502 b/examples/basicrhstoken.6502 deleted file mode 100644 index e46a8c2..0000000 --- a/examples/basicrhstoken.6502 +++ /dev/null @@ -1 +0,0 @@ -putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/examples/basicrhstoken.bas b/examples/basicrhstoken.bas deleted file mode 100644 index f2d9a47..0000000 --- a/examples/basicrhstoken.bas +++ /dev/null @@ -1,13 +0,0 @@ -p=PAGE -PRINT p -?&900=PAGE MOD 256 -PRINT FNpage -PRINT ?&900 -?&901=TIME MOD 256 -PRINT ?&901 -PRINT FNtime -END -DEF FNpage -=PAGE -DEF FNtime -=TIME diff --git a/examples/errorlinenumber1.6502 b/examples/errorlinenumber1.6502 deleted file mode 100644 index f7d1382..0000000 --- a/examples/errorlinenumber1.6502 +++ /dev/null @@ -1,17 +0,0 @@ -org &2000 -.start - -macro foo - for i, 0, 2 - nop - next - - lda #10 - ldx (&70),y -endmacro - -foo - -.end - -save "foo", start, end diff --git a/examples/errorlinenumber2.6502 b/examples/errorlinenumber2.6502 deleted file mode 100644 index b585b14..0000000 --- a/examples/errorlinenumber2.6502 +++ /dev/null @@ -1,15 +0,0 @@ -org &2000 -.start - -for i, 0, 2 - nop -next - -lda #10 -ldx (&70),y - -foo - -.end - -save "foo", start, end diff --git a/examples/invalidbasic1.6502 b/examples/invalidbasic1.6502 deleted file mode 100644 index 74738f2..0000000 --- a/examples/invalidbasic1.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "invalidbasic1.bas", "ib1" diff --git a/examples/invalidbasic1.bas b/examples/invalidbasic1.bas deleted file mode 100644 index 62eb4c3..0000000 --- a/examples/invalidbasic1.bas +++ /dev/null @@ -1 +0,0 @@ -10PRINT "Hello diff --git a/examples/invalidbasic2.6502 b/examples/invalidbasic2.6502 deleted file mode 100644 index b46d583..0000000 --- a/examples/invalidbasic2.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "invalidbasic2.bas", "ib2" diff --git a/examples/invalidbasic2.bas b/examples/invalidbasic2.bas deleted file mode 100644 index 7482ead..0000000 --- a/examples/invalidbasic2.bas +++ /dev/null @@ -1 +0,0 @@ -10REM This line just goes on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on... diff --git a/examples/local-forward-branch-1.6502 b/examples/local-forward-branch-1.6502 deleted file mode 100644 index 161a6e2..0000000 --- a/examples/local-forward-branch-1.6502 +++ /dev/null @@ -1,20 +0,0 @@ -org &2000 - -.start - -.ok - for i,1,65 - lda #65 - next - { - cmp #65:beq ok:jmp boom:.ok - } - rts - -.boom - lda #7:jsr &ffee - jmp boom - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-2.6502 b/examples/local-forward-branch-2.6502 deleted file mode 100644 index e46fd65..0000000 --- a/examples/local-forward-branch-2.6502 +++ /dev/null @@ -1,24 +0,0 @@ -org &2000 - -macro branch_if_eq target - beq target -endmacro - -.start - -.ok - for i,1,65 - lda #65 - next - { - cmp #65:branch_if_eq ok:jmp boom:.ok - } - rts - -.boom - lda #7:jsr &ffee - jmp boom - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-3.6502 b/examples/local-forward-branch-3.6502 deleted file mode 100644 index 758f8b1..0000000 --- a/examples/local-forward-branch-3.6502 +++ /dev/null @@ -1,15 +0,0 @@ -org &2000 - -a = &70 - -macro foo zp - lda zp -endmacro - -.start - - foo a - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-4.6502 b/examples/local-forward-branch-4.6502 deleted file mode 100644 index aa06f44..0000000 --- a/examples/local-forward-branch-4.6502 +++ /dev/null @@ -1,19 +0,0 @@ -org &2000 - -a = &70 - -macro load_a_and_maybe_x i, do_x - lda #i - if do_x - ldx #i - endif -endmacro - -.start - - load_a_and_maybe_x 42, FALSE - load_a_and_maybe_x 65, TRUE - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-5.6502 b/examples/local-forward-branch-5.6502 deleted file mode 100644 index 852472f..0000000 --- a/examples/local-forward-branch-5.6502 +++ /dev/null @@ -1,30 +0,0 @@ -org &2000 - -\ Some buggy changes to the assembler caused the values emitted by equw -\ to be incorrect; no obvious failure occurred. So it's important to check -\ the output of this to see that it's right, unlike the earlier -\ local-forward-branch-n.6502 tests which simply failed to assemble. - -macro bar x - equw x -endmacro - -macro foo x - bar x-1 -endmacro - -.start - - lda #65 -.baz - - foo start ; should emit &ff &1f - foo baz ; should emit &01 &20 - - ldx #42 - - foo end ; should emit &09 &20 - -.end - -save "test", start, end diff --git a/examples/putbasicnonexistentdemo.6502 b/examples/putbasicnonexistentdemo.6502 deleted file mode 100644 index 790fce6..0000000 --- a/examples/putbasicnonexistentdemo.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "nonexistent", "ne" diff --git a/examples/putfilenonexistentdemo.6502 b/examples/putfilenonexistentdemo.6502 deleted file mode 100644 index fe9dcd8..0000000 --- a/examples/putfilenonexistentdemo.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putfile "nonexistent", "ne", 0, 0 --- examples/basicrhstoken.6502 | 1 - examples/basicrhstoken.bas | 13 ------------ examples/errorlinenumber1.6502 | 17 --------------- examples/errorlinenumber2.6502 | 15 -------------- examples/invalidbasic1.6502 | 7 ------- examples/invalidbasic1.bas | 1 - examples/invalidbasic2.6502 | 7 ------- examples/invalidbasic2.bas | 1 - examples/local-forward-branch-1.6502 | 20 ------------------ examples/local-forward-branch-2.6502 | 24 --------------------- examples/local-forward-branch-3.6502 | 15 -------------- examples/local-forward-branch-4.6502 | 19 ----------------- examples/local-forward-branch-5.6502 | 30 --------------------------- examples/putbasicnonexistentdemo.6502 | 7 ------- examples/putfilenonexistentdemo.6502 | 7 ------- 15 files changed, 184 deletions(-) delete mode 100644 examples/basicrhstoken.6502 delete mode 100644 examples/basicrhstoken.bas delete mode 100644 examples/errorlinenumber1.6502 delete mode 100644 examples/errorlinenumber2.6502 delete mode 100644 examples/invalidbasic1.6502 delete mode 100644 examples/invalidbasic1.bas delete mode 100644 examples/invalidbasic2.6502 delete mode 100644 examples/invalidbasic2.bas delete mode 100644 examples/local-forward-branch-1.6502 delete mode 100644 examples/local-forward-branch-2.6502 delete mode 100644 examples/local-forward-branch-3.6502 delete mode 100644 examples/local-forward-branch-4.6502 delete mode 100644 examples/local-forward-branch-5.6502 delete mode 100644 examples/putbasicnonexistentdemo.6502 delete mode 100644 examples/putfilenonexistentdemo.6502 diff --git a/examples/basicrhstoken.6502 b/examples/basicrhstoken.6502 deleted file mode 100644 index e46a8c2..0000000 --- a/examples/basicrhstoken.6502 +++ /dev/null @@ -1 +0,0 @@ -putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/examples/basicrhstoken.bas b/examples/basicrhstoken.bas deleted file mode 100644 index f2d9a47..0000000 --- a/examples/basicrhstoken.bas +++ /dev/null @@ -1,13 +0,0 @@ -p=PAGE -PRINT p -?&900=PAGE MOD 256 -PRINT FNpage -PRINT ?&900 -?&901=TIME MOD 256 -PRINT ?&901 -PRINT FNtime -END -DEF FNpage -=PAGE -DEF FNtime -=TIME diff --git a/examples/errorlinenumber1.6502 b/examples/errorlinenumber1.6502 deleted file mode 100644 index f7d1382..0000000 --- a/examples/errorlinenumber1.6502 +++ /dev/null @@ -1,17 +0,0 @@ -org &2000 -.start - -macro foo - for i, 0, 2 - nop - next - - lda #10 - ldx (&70),y -endmacro - -foo - -.end - -save "foo", start, end diff --git a/examples/errorlinenumber2.6502 b/examples/errorlinenumber2.6502 deleted file mode 100644 index b585b14..0000000 --- a/examples/errorlinenumber2.6502 +++ /dev/null @@ -1,15 +0,0 @@ -org &2000 -.start - -for i, 0, 2 - nop -next - -lda #10 -ldx (&70),y - -foo - -.end - -save "foo", start, end diff --git a/examples/invalidbasic1.6502 b/examples/invalidbasic1.6502 deleted file mode 100644 index 74738f2..0000000 --- a/examples/invalidbasic1.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "invalidbasic1.bas", "ib1" diff --git a/examples/invalidbasic1.bas b/examples/invalidbasic1.bas deleted file mode 100644 index 62eb4c3..0000000 --- a/examples/invalidbasic1.bas +++ /dev/null @@ -1 +0,0 @@ -10PRINT "Hello diff --git a/examples/invalidbasic2.6502 b/examples/invalidbasic2.6502 deleted file mode 100644 index b46d583..0000000 --- a/examples/invalidbasic2.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "invalidbasic2.bas", "ib2" diff --git a/examples/invalidbasic2.bas b/examples/invalidbasic2.bas deleted file mode 100644 index 7482ead..0000000 --- a/examples/invalidbasic2.bas +++ /dev/null @@ -1 +0,0 @@ -10REM This line just goes on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on and on... diff --git a/examples/local-forward-branch-1.6502 b/examples/local-forward-branch-1.6502 deleted file mode 100644 index 161a6e2..0000000 --- a/examples/local-forward-branch-1.6502 +++ /dev/null @@ -1,20 +0,0 @@ -org &2000 - -.start - -.ok - for i,1,65 - lda #65 - next - { - cmp #65:beq ok:jmp boom:.ok - } - rts - -.boom - lda #7:jsr &ffee - jmp boom - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-2.6502 b/examples/local-forward-branch-2.6502 deleted file mode 100644 index e46fd65..0000000 --- a/examples/local-forward-branch-2.6502 +++ /dev/null @@ -1,24 +0,0 @@ -org &2000 - -macro branch_if_eq target - beq target -endmacro - -.start - -.ok - for i,1,65 - lda #65 - next - { - cmp #65:branch_if_eq ok:jmp boom:.ok - } - rts - -.boom - lda #7:jsr &ffee - jmp boom - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-3.6502 b/examples/local-forward-branch-3.6502 deleted file mode 100644 index 758f8b1..0000000 --- a/examples/local-forward-branch-3.6502 +++ /dev/null @@ -1,15 +0,0 @@ -org &2000 - -a = &70 - -macro foo zp - lda zp -endmacro - -.start - - foo a - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-4.6502 b/examples/local-forward-branch-4.6502 deleted file mode 100644 index aa06f44..0000000 --- a/examples/local-forward-branch-4.6502 +++ /dev/null @@ -1,19 +0,0 @@ -org &2000 - -a = &70 - -macro load_a_and_maybe_x i, do_x - lda #i - if do_x - ldx #i - endif -endmacro - -.start - - load_a_and_maybe_x 42, FALSE - load_a_and_maybe_x 65, TRUE - -.end - -save "test", start, end diff --git a/examples/local-forward-branch-5.6502 b/examples/local-forward-branch-5.6502 deleted file mode 100644 index 852472f..0000000 --- a/examples/local-forward-branch-5.6502 +++ /dev/null @@ -1,30 +0,0 @@ -org &2000 - -\ Some buggy changes to the assembler caused the values emitted by equw -\ to be incorrect; no obvious failure occurred. So it's important to check -\ the output of this to see that it's right, unlike the earlier -\ local-forward-branch-n.6502 tests which simply failed to assemble. - -macro bar x - equw x -endmacro - -macro foo x - bar x-1 -endmacro - -.start - - lda #65 -.baz - - foo start ; should emit &ff &1f - foo baz ; should emit &01 &20 - - ldx #42 - - foo end ; should emit &09 &20 - -.end - -save "test", start, end diff --git a/examples/putbasicnonexistentdemo.6502 b/examples/putbasicnonexistentdemo.6502 deleted file mode 100644 index 790fce6..0000000 --- a/examples/putbasicnonexistentdemo.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putbasic "nonexistent", "ne" diff --git a/examples/putfilenonexistentdemo.6502 b/examples/putfilenonexistentdemo.6502 deleted file mode 100644 index fe9dcd8..0000000 --- a/examples/putfilenonexistentdemo.6502 +++ /dev/null @@ -1,7 +0,0 @@ -org &2000 -.start - rts -.end - -save "test", start, end -putfile "nonexistent", "ne", 0, 0 From 64e9ffd727822b712fe2eaa60c6ecd150209c182 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Fri, 9 Jul 2021 20:01:11 +0100 Subject: [PATCH 076/144] Include "sphere" demo as a test case diff --git a/test/6-projects/demo.6502 b/test/6-projects/demo.6502 new file mode 100644 index 0000000..936d03f --- /dev/null +++ b/test/6-projects/demo.6502 @@ -0,0 +1,4 @@ +\ Assemble the "sphere" demo (demo.6502) included at the top level of the +\ repository as a test. + +INCLUDE "../../demo.6502" diff --git a/test/6-projects/demo.gold.ssd b/test/6-projects/demo.gold.ssd new file mode 100644 index 0000000..53ca48d Binary files /dev/null and b/test/6-projects/demo.gold.ssd differ --- test/6-projects/demo.6502 | 4 ++++ test/6-projects/demo.gold.ssd | Bin 0 -> 2816 bytes 2 files changed, 4 insertions(+) create mode 100644 test/6-projects/demo.6502 create mode 100644 test/6-projects/demo.gold.ssd diff --git a/test/6-projects/demo.6502 b/test/6-projects/demo.6502 new file mode 100644 index 0000000..936d03f --- /dev/null +++ b/test/6-projects/demo.6502 @@ -0,0 +1,4 @@ +\ Assemble the "sphere" demo (demo.6502) included at the top level of the +\ repository as a test. + +INCLUDE "../../demo.6502" diff --git a/test/6-projects/demo.gold.ssd b/test/6-projects/demo.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..53ca48d986485d2e6f7f045515086048b9ecb795 GIT binary patch literal 2816 zcmd^;dpJ~S7{I@|k0H%WjBy{A!5HL{sL14!lKZWMN=q(F$);6UiYc{wEM0dh8@92I z65DD~rcxo7O0CUiDN;7wR#q!5$9^-|c%J^)=jrL$Kl(k-obNl|ch38scYfaszywa4 z!Xr2wj{fBB$2Cw19KZmKd@8_yT!jOWR5*Sl+ZoGt!O%3s6GNOZ^fDaL*@z>C5)c7~ z0%Qe-79m7D%0x)`LIDbjML=+^_00<#MpNrh;jC<cN!pnR4r`(hp+g7<E1WHP#6)y3 zxpa7Gq7R}#NET9rBq0^W3FTIaNdu#BiBt%7(IkK^#IYqnC4y4Sjur?Ns)3RaZ(9xK z2<69sYB_QFcr@-(dU3f1d`LAyzNDOHD*?(QK-v8sLjqV6lec@-KzU5IC!4UOA%Pg@ z=ElQH#DIs<XhOmXnh2=vg&RZ>P#5EP6cNxU76Ugi-nc~4z+@0|3Hl1=^ej5Lo>))J zr%bLD(H?i_4-ni_8Pe}>#Fs}&YbdD!YMF!@pq@#r0UEi4f8WX_z{gvA*kW=7RY(?7 zBIJY=)DiK*O1&_;JX=hSrVFV#_&zx_Y--U~Oznq@<@kCzG=5^yoZz0!h#eg-^}u-e zRvIcmJg`2JcvLz<G0qq|4{^cZJpgzdl}ICT=(-FeC1d3@L9mel;OGJhhe`#MIR=(4 zKH>52I^1LhCR>}!x3IBycJm4d4vUOSNJ`DfLiQa#R#0@ZtfH!>wyv?IwXMCQtGl<a z|LOCA!J${fuiyMVIyQ!3*f{)+jg9{O=JoKap}~RYPy73NySqBt+ge*1>uPJND#}h4 z6&yRf56Q|%O-hK13=0nMa&xw~vEXyH*-Qm8_@DC=Wss&ro&n|km=TLSJq>9hwd_im z4t@r>_hUvZ{PZ*gS>|^giRo)Iv%ift6=|x7$;u|mo%qYBQsiXNq{u@Qq|AaSLS{qg z5C((^nFCQ$Qc{MnSS%G46*il#3Q<#2Q&(5l(9qD-)YQ__($?1IaCCHZbai$0^z`-h zxm>P+fuW(Hkr9u_GdAY)`6ec&rlw|QbLX0yTUc0FT3T7nn`dopJ%7H9jjip11q&B0 zT4ZNuZ@+l4gM&b@WQn7rlasTvi_6lbuCB|LEnn{Dw!+=r!^6|l%gfux$Jf`-&)+{F zFmUC{RY5_kR|kiLgodtJyLR2Wu=VRd2@j8m*bo^R6&)QD^J#2s+{TTY;^Q|bBz(4I zOXAk8pC@hGwmmsHB{lVnw6ydcJ9lPeW(s%h-kr5)@7^!JLXhmQb8>R`?caak;Gsi@ zzY&R!96gqo_wDig{DQ*oPMi=IeP4{CB_~gnmYyyv|KZG;A1luOR9RV7eeQgXL~`Nc zrP|9^u3oLHZ@AXj)ZE;1{l?AKTep91Yy0I+`>%I9e(UVIcfY%*=RxnoM}5CP?tk+1 z+4C2F47?otbLiDy!>>oA9gK|+7gW*~Uf#0>bf(^FP8V%|*bvvX{)rqb$Twyv|0w@u zXU_|fp;^l5M7&~n{jR(|-&qzLJxogN9t6_$t)l#qT$2JH?`PLX)({6B^XhVS1x;mv zXR{v{M|+2eDYm(S)#p1ZnpE!xA)c#>L0dT2+u=#giqIUPxTJu!p*ghP@}L{=>klPI z#Kw-qIA$I&aV7aVpV|>{r{Q8&NdIZS>1Xjn%r&$)I(l3KBOc$>%-q7tdcN&~MRtoF zmN+`QxGsa|z|+gy*Uvu?st*o<*4IJf(05cclpP0EZ-$~16Q!QFC#R%9%TRJghSc$- zVrUo&hJK-5somo;wNkNAE0hYAR#u%mFS&53_R7`zhQ_9r8#iy=ZoAWdx1;mk{hr<r zQS<beWO+q8Q;Ee^Ra4j0(uQrNs|Onj_LH=mjHTUVBI_pDPSS>gm`?&b>pwH~jx2yQ v66{Y?7?{t8DR?|1BSQlNE>~YqS4W4Vt);21rpjh1G3ko(<mun~A?E)8$_~eJ literal 0 HcmV?d00001 From d0902e7ca7b0f6850e062eaf36cd09fcef62e608 Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Fri, 9 Jul 2021 20:04:51 +0100 Subject: [PATCH 077/144] Add gold ssd for test-local-forward-branch-5 As noted in the comments in this test, this code was able to trigger a bug in an older (development) build of beebasm where the code assembled without errors but the generated output was incorrect. diff --git a/test/4-assembler/local-forward-branch-5.gold.ssd b/test/4-assembler/local-forward-branch-5.gold.ssd new file mode 100644 index 0000000..eb345d7 Binary files /dev/null and b/test/4-assembler/local-forward-branch-5.gold.ssd differ --- test/4-assembler/local-forward-branch-5.gold.ssd | Bin 0 -> 768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/4-assembler/local-forward-branch-5.gold.ssd diff --git a/test/4-assembler/local-forward-branch-5.gold.ssd b/test/4-assembler/local-forward-branch-5.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..eb345d7126725376ab7bb74b6db923ae0859ee0a GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV#lXNcvOLbfu+s6rJfp%QEl!1z=5vUD0k$ax A+5i9m literal 0 HcmV?d00001 From c9efec486a1fcb86a9c79ddc41b25fcfe7a673fa Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Fri, 9 Jul 2021 20:25:07 +0100 Subject: [PATCH 078/144] Add gold ssd for basicrhstoken test This used to assemble correctly but the generated BASIC was badly tokenised; to verify the test has succeeded it's important to check the tokenised output. diff --git a/test/3-directives/basicrhstoken.gold.ssd b/test/3-directives/basicrhstoken.gold.ssd new file mode 100644 index 0000000..ef5d6ce Binary files /dev/null and b/test/3-directives/basicrhstoken.gold.ssd differ --- test/3-directives/basicrhstoken.gold.ssd | Bin 0 -> 768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/3-directives/basicrhstoken.gold.ssd diff --git a/test/3-directives/basicrhstoken.gold.ssd b/test/3-directives/basicrhstoken.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..ef5d6ce02da70cebc5a0a21cf4d97ad6310d7a98 GIT binary patch literal 768 zcmZQzfPf&6;1GXr1r;cFWKkT<3Jj9U4Xq4km`0Yz85npO7}*PKC-5>bv42!3;ALPI zv{$n<FtDAV(5zr&YR1dJ!u?TUNkL+IDlY>okO`LMWncp<GqjxuQ^pRJGvsC90O=^n k%mwP;WPQNPz{PzRW&$@G$SNL?07L;Vo9#s2{{tB40B-*y;Q#;t literal 0 HcmV?d00001 From d9a78b9a8545a8152e5631e45b869c2bb7d03684 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 12 Jul 2021 22:51:42 +0100 Subject: [PATCH 079/144] Random number generator using 32-bit ints Previously it was using the long type, which is 64-bit on 64-bit Linux but always 32-bit on Windows, so the RNG gave inconsistent results. This does mean that the output of beebasm on 64-bit Linux will not be consistent with older versions. --- src/random.cpp | 10 ++++++---- src/random.h | 4 ++-- test/6-projects/demo.gold.ssd | Bin 2816 -> 2816 bytes 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index b1c94a5..f7cf057 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -24,13 +24,14 @@ */ /*************************************************************************************************/ +#include <cstdio> #include "random.h" -static unsigned long state = 19670512; +static unsigned int state = 19670512; -static unsigned long modulus = BEEBASM_RAND_MODULUS; +static unsigned int modulus = BEEBASM_RAND_MODULUS; -void beebasm_srand(unsigned long seed) +void beebasm_srand(unsigned int seed) { state = seed % modulus; if ( state == 0 ) @@ -47,8 +48,9 @@ void beebasm_srand(unsigned long seed) } } -unsigned long beebasm_rand() +unsigned int beebasm_rand() { + //printf("RND %i %lu\n", ( BEEBASM_RAND_MULTIPLIER * state ) % modulus, ( BEEBASM_RAND_MULTIPLIER * (int)state ) % modulus); state = ( BEEBASM_RAND_MULTIPLIER * state ) % modulus; // It's always true that 1 <= state <= (modulus - 1), so we return state - 1 to make // 0 a possible value. BEEBASM_RAND_MAX is modulus - 2, so we have 0 <= return value <= diff --git a/src/random.h b/src/random.h index 3d45bb6..e2d2ac0 100644 --- a/src/random.h +++ b/src/random.h @@ -28,8 +28,8 @@ #define BEEBASM_RAND_MODULUS (2147483647UL) #define BEEBASM_RAND_MAX (BEEBASM_RAND_MODULUS - 2UL) -void beebasm_srand(unsigned long seed); +void beebasm_srand(unsigned int seed); -unsigned long beebasm_rand(); +unsigned int beebasm_rand(); #endif // RANDOM_H_ diff --git a/test/6-projects/demo.gold.ssd b/test/6-projects/demo.gold.ssd index 53ca48d986485d2e6f7f045515086048b9ecb795..d7e705aeffd12cc0f283f65a7c1ba6933c00f09b 100644 GIT binary patch delta 174 zcmV;f08#&d7JwF@2!C4&IR+IEymkIH5QOTEpZ<Xx>}lmL<cf~5LJ+_a2N}ovPe=d6 z?xrMLIQedzS&wdMk&Om=Zo;A}6P-#Lv8RZ{*$8yHHM^m<$SHI8FlUwkdh>3wHTTs% z(qxtpRt>!w)o;uPds77p1>V)Q)(PtA0D6{ljnv`wMz^BC2qA9@VCs^PPujr23Z!R- cRfW^C1(e~+SPybc{YB<^TtYjM5n!ST0`SLD#sB~S delta 174 zcmV;f08#&d7JwF@2!HpXw+cGKiF5$mebLK+nR?{MZQWw(2^LvWYV@F+EAian?wKbp ze6Do@4rIoLo#s*vF>XyPtvupZ5F|5bRE?1=p;1um&G}vg@<N@(ks?6KuU55==%{H> zT%`v%kw99z+O*0U-&u`LSf~KhWF=5P>by%`kcFkHp%!M#T_MIYl}Z3p>0Jh8Y;5^z cLWGnoM+H+vtbtY5$G?bN>8?|e5n!ST0?WBlFaQ7m From b6ec0f5ee404eb9a8812445d7ad6bb0b2b5b610a Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 12 Jul 2021 23:23:46 +0100 Subject: [PATCH 080/144] Previous RNG fix did not change type of constants --- src/random.cpp | 2 -- src/random.h | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index f7cf057..2b6d7a8 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -24,7 +24,6 @@ */ /*************************************************************************************************/ -#include <cstdio> #include "random.h" static unsigned int state = 19670512; @@ -50,7 +49,6 @@ void beebasm_srand(unsigned int seed) unsigned int beebasm_rand() { - //printf("RND %i %lu\n", ( BEEBASM_RAND_MULTIPLIER * state ) % modulus, ( BEEBASM_RAND_MULTIPLIER * (int)state ) % modulus); state = ( BEEBASM_RAND_MULTIPLIER * state ) % modulus; // It's always true that 1 <= state <= (modulus - 1), so we return state - 1 to make // 0 a possible value. BEEBASM_RAND_MAX is modulus - 2, so we have 0 <= return value <= diff --git a/src/random.h b/src/random.h index e2d2ac0..10c8c55 100644 --- a/src/random.h +++ b/src/random.h @@ -24,8 +24,8 @@ #ifndef RANDOM_H_ #define RANDOM_H_ -#define BEEBASM_RAND_MULTIPLIER (48271UL) -#define BEEBASM_RAND_MODULUS (2147483647UL) +#define BEEBASM_RAND_MULTIPLIER (48271U) +#define BEEBASM_RAND_MODULUS (2147483647U) #define BEEBASM_RAND_MAX (BEEBASM_RAND_MODULUS - 2UL) void beebasm_srand(unsigned int seed); From 8317a33a4eac62c65e322192850570e001f9aa03 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 10:09:03 +0100 Subject: [PATCH 081/144] RNG - the values are 32-bit but the calculation is 64-bit Use uint_least32_t and uint_least64_t to make this explicit. This breaks C++98 compatibility, but they come from C99 so nearly every C++98 compiler will support them. This is now producing the same random numbers on all platforms as previous 64-bit Linux builds. Windows results have changed. --- src/random.cpp | 10 +++++----- src/random.h | 12 +++++++----- test/6-projects/demo.gold.ssd | Bin 2816 -> 2816 bytes 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 2b6d7a8..bf9fbee 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -26,11 +26,11 @@ #include "random.h" -static unsigned int state = 19670512; +static uint_least32_t state = 19670512; -static unsigned int modulus = BEEBASM_RAND_MODULUS; +static uint_least32_t modulus = BEEBASM_RAND_MODULUS; -void beebasm_srand(unsigned int seed) +void beebasm_srand(uint_least32_t seed) { state = seed % modulus; if ( state == 0 ) @@ -47,9 +47,9 @@ void beebasm_srand(unsigned int seed) } } -unsigned int beebasm_rand() +uint_least32_t beebasm_rand() { - state = ( BEEBASM_RAND_MULTIPLIER * state ) % modulus; + state = ( static_cast<uint_least64_t>(BEEBASM_RAND_MULTIPLIER) * state ) % modulus; // It's always true that 1 <= state <= (modulus - 1), so we return state - 1 to make // 0 a possible value. BEEBASM_RAND_MAX is modulus - 2, so we have 0 <= return value <= // BEEBASM_RAND_MAX as required for compatibility with the interface of rand(). diff --git a/src/random.h b/src/random.h index 10c8c55..c083289 100644 --- a/src/random.h +++ b/src/random.h @@ -24,12 +24,14 @@ #ifndef RANDOM_H_ #define RANDOM_H_ -#define BEEBASM_RAND_MULTIPLIER (48271U) -#define BEEBASM_RAND_MODULUS (2147483647U) -#define BEEBASM_RAND_MAX (BEEBASM_RAND_MODULUS - 2UL) +#include <stdint.h> -void beebasm_srand(unsigned int seed); +#define BEEBASM_RAND_MULTIPLIER static_cast<uint_least32_t>(48271) +#define BEEBASM_RAND_MODULUS static_cast<uint_least32_t>(2147483647) +#define BEEBASM_RAND_MAX (BEEBASM_RAND_MODULUS - static_cast<uint_least32_t>(2)) -unsigned int beebasm_rand(); +void beebasm_srand(uint_least32_t seed); + +uint_least32_t beebasm_rand(); #endif // RANDOM_H_ diff --git a/test/6-projects/demo.gold.ssd b/test/6-projects/demo.gold.ssd index d7e705aeffd12cc0f283f65a7c1ba6933c00f09b..53ca48d986485d2e6f7f045515086048b9ecb795 100644 GIT binary patch delta 174 zcmV;f08#&d7JwF@2!HpXw+cGKiF5$mebLK+nR?{MZQWw(2^LvWYV@F+EAian?wKbp ze6Do@4rIoLo#s*vF>XyPtvupZ5F|5bRE?1=p;1um&G}vg@<N@(ks?6KuU55==%{H> zT%`v%kw99z+O*0U-&u`LSf~KhWF=5P>by%`kcFkHp%!M#T_MIYl}Z3p>0Jh8Y;5^z cLWGnoM+H+vtbtY5$G?bN>8?|e5n!ST0?WBlFaQ7m delta 174 zcmV;f08#&d7JwF@2!C4&IR+IEymkIH5QOTEpZ<Xx>}lmL<cf~5LJ+_a2N}ovPe=d6 z?xrMLIQedzS&wdMk&Om=Zo;A}6P-#Lv8RZ{*$8yHHM^m<$SHI8FlUwkdh>3wHTTs% z(qxtpRt>!w)o;uPds77p1>V)Q)(PtA0D6{ljnv`wMz^BC2qA9@VCs^PPujr23Z!R- cRfW^C1(e~+SPybc{YB<^TtYjM5n!ST0`SLD#sB~S From 5acee16d5c7f319873e221383cb3c98a86e484ca Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 17:30:39 +0100 Subject: [PATCH 082/144] Add gold ssds for all the productive tests Add a commented-out failure test from scopejumpdemo2. Add gold.txt for filelinecallstackdemo. Checked all the ssds using EXMON. --- test/3-directives/autolinenumdemo.gold.ssd | Bin 0 -> 1280 bytes .../local-forward-branch-1.gold.ssd | Bin 0 -> 768 bytes .../local-forward-branch-2.gold.ssd | Bin 0 -> 768 bytes .../local-forward-branch-3.gold.ssd | Bin 0 -> 768 bytes .../local-forward-branch-4.gold.ssd | Bin 0 -> 768 bytes test/4-assembler/scopejumpdemo1.gold.ssd | Bin 0 -> 768 bytes test/4-assembler/scopejumpdemo2.6502 | 2 +- test/4-assembler/scopejumpdemo2.fail.6502 | 19 +++++++++++++++ test/4-assembler/scopejumpdemo2.gold.ssd | Bin 0 -> 768 bytes test/5-errors/filelinecallstackdemo.gold.ssd | Bin 0 -> 768 bytes test/5-errors/filelinecallstackdemo.gold.txt | 22 ++++++++++++++++++ 11 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/3-directives/autolinenumdemo.gold.ssd create mode 100644 test/4-assembler/local-forward-branch-1.gold.ssd create mode 100644 test/4-assembler/local-forward-branch-2.gold.ssd create mode 100644 test/4-assembler/local-forward-branch-3.gold.ssd create mode 100644 test/4-assembler/local-forward-branch-4.gold.ssd create mode 100644 test/4-assembler/scopejumpdemo1.gold.ssd create mode 100644 test/4-assembler/scopejumpdemo2.fail.6502 create mode 100644 test/4-assembler/scopejumpdemo2.gold.ssd create mode 100644 test/5-errors/filelinecallstackdemo.gold.ssd create mode 100644 test/5-errors/filelinecallstackdemo.gold.txt diff --git a/test/3-directives/autolinenumdemo.gold.ssd b/test/3-directives/autolinenumdemo.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..785c8d84b540b259672d4127dcd0cb04a7d52fa9 GIT binary patch literal 1280 zcmd^-Piqu07{=f2?y99t74*{E!@5GNb(y8d;-N~FN>OO{1DKg)XM!`ym}EMRC%;oK z{SbZx1;w*M>w*_Svs3?p_z5<bJOuK*`TYpd#p1aZvM@t0od+=ddAu0gwJvD==IhU$ zH(dguL$tIQ;6%?B1@u3hj5-H194IZJt0I=xf!mNx4O4n$G|!<l?(ZJ$KR{_sYI)&M zIVn&ppTTDmW#wb;RKjR!ZHB}Y1=nK4NIW{jAg(+bQz79RsXCVP8wJ#~J*>I*TX3eW zNiwce3FQ#;<ay1laCZkgjP&(T6Ix5|Rh-MwfRVZS5&O69jd6-`#HhUS6&vFyite0j zY;WEkN1KdZtb9RjtQaz~D`&r=>wjY8SI<*pMBGS5RsTJs<Dg)-<kn+qP4Q>^oTZ(r za^;&X#8T-LJUjy4z?Cvl(+2rp@)*^RXLE$WA{tDMZPD!9X~La!s52Szg-<P|aIKIa zp-M7v*N&{b6pUW3&v3YRG<kZs2j{t$h1A}SSoeJSZD=;E`z?f+Th_bOhF7e2dF>s3 Z!}YGL26L0i!zTmQTkd~g-~astKLE28t^EK1 literal 0 HcmV?d00001 diff --git a/test/4-assembler/local-forward-branch-1.gold.ssd b/test/4-assembler/local-forward-branch-1.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..b2ebcd9431470746f5a03619bb4158ab2adce626 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFviGhJ>WO<x{VWs0h!;_95n0>kw5>~P+y!#Ji H&^HDEZeK;T literal 0 HcmV?d00001 diff --git a/test/4-assembler/local-forward-branch-2.gold.ssd b/test/4-assembler/local-forward-branch-2.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..b2ebcd9431470746f5a03619bb4158ab2adce626 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFviGhJ>WO<x{VWs0h!;_95n0>kw5>~P+y!#Ji H&^HDEZeK;T literal 0 HcmV?d00001 diff --git a/test/4-assembler/local-forward-branch-3.gold.ssd b/test/4-assembler/local-forward-branch-3.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..d94a8e4db5ab3240d18bf3cb5a2e66fdaa734d9f GIT binary patch literal 768 qcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV#J~VVBMU4o7+L;B_bmXXl>^BD literal 0 HcmV?d00001 diff --git a/test/4-assembler/local-forward-branch-4.gold.ssd b/test/4-assembler/local-forward-branch-4.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..3b24cd7850908561b25e0cf34c9599c6005a5ed8 GIT binary patch literal 768 wcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV#=yWdvOLbfuu^NK<08kA=4*t10W;?XSO5S3 literal 0 HcmV?d00001 diff --git a/test/4-assembler/scopejumpdemo1.gold.ssd b/test/4-assembler/scopejumpdemo1.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..f4e9c1fd4b625d9c74a1a82487b10bb9e87014aa GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV%fP@ivOLbfu;~A(3x5|i00|#fg^}iYpnn13 CFAE_6 literal 0 HcmV?d00001 diff --git a/test/4-assembler/scopejumpdemo2.6502 b/test/4-assembler/scopejumpdemo2.6502 index a22564c..c9b3f0f 100644 --- a/test/4-assembler/scopejumpdemo2.6502 +++ b/test/4-assembler/scopejumpdemo2.6502 @@ -8,7 +8,7 @@ ORG &2000 JSR bar \ JMP ldy_3_and_rts \ this won't assemble, even though it exists inside - \ scopejumpdemo2foo.6502 + \ scopejumpdemo2foo.6502 LDY #3 RTS diff --git a/test/4-assembler/scopejumpdemo2.fail.6502 b/test/4-assembler/scopejumpdemo2.fail.6502 new file mode 100644 index 0000000..f487f51 --- /dev/null +++ b/test/4-assembler/scopejumpdemo2.fail.6502 @@ -0,0 +1,19 @@ +ORG &2000 + +.start + + LDA #65:STA &70 + JSR foo + STY &71 + JSR bar + + JMP ldy_3_and_rts \ this won't assemble, even though it exists inside + \ scopejumpdemo2foo.6502 + LDY #3 + RTS + +INCLUDE "scopejumpdemo2.inc.6502" + +.end + +SAVE "test", start, end diff --git a/test/4-assembler/scopejumpdemo2.gold.ssd b/test/4-assembler/scopejumpdemo2.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..5669df2096ae0429f0b1c846fdf1744595c3ae07 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgG~&%nSmvOLbfu+p)$K!IPOrBFd$VF7c((t?v( b6Id59`N$|NVgd0B!F+ZQpA$$7yWjx;oM92# literal 0 HcmV?d00001 diff --git a/test/5-errors/filelinecallstackdemo.gold.ssd b/test/5-errors/filelinecallstackdemo.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..ab96c569ca396771193abf622fc4a6caea4c54d3 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFfz`(#XvOLbfus~}O!%B&{3<f7&JYc!-HDRRr F9{?Ya3UvSg literal 0 HcmV?d00001 diff --git a/test/5-errors/filelinecallstackdemo.gold.txt b/test/5-errors/filelinecallstackdemo.gold.txt new file mode 100644 index 0000000..62df925 --- /dev/null +++ b/test/5-errors/filelinecallstackdemo.gold.txt @@ -0,0 +1,22 @@ +.start + 2000 A0 2A LDY #&2A +Current location:filelinecallstackdemo.6502:14 + 2002 A2 00 LDX #&00 +.loop +Macro foo: +Macro bar: + 2004 A9 18 LDA #&18 +Current call stack:filelinecallstackdemo.6502:9 +filelinecallstackdemo.6502:4 +filelinecallstackdemo.6502:17 (end) +End macro bar +End macro foo + 2006 9D 00 30 STA &3000,X + 2009 C8 INY + 200A E8 INX + 200B E0 04 CPX #&04 + 200D D0 F5 BNE &2004 + 200F 60 RTS +.end +Saving file 'test' +Processed file 'filelinecallstackdemo.6502' ok From 1a714a783ebc47b37e089da009f00f26a258c75c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 20:50:39 +0100 Subject: [PATCH 083/144] Catch all errors parsing hex constants This won't break existing code, it just improves the error message --- src/expression.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 1f767bc..8218d1c 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -154,28 +154,25 @@ Value LineParser::GetValue() } else if ( m_column < m_line.length() && ( m_line[ m_column ] == '&' || m_line[ m_column ] == '$' ) ) { - // get a hex digit + // get hexadecimal + // skip the number prefix m_column++; - if ( m_column >= m_line.length() || !isxdigit( m_line[ m_column ] ) ) - { - // badly formed hex literal - throw AsmException_SyntaxError_BadHex( m_line, m_column ); - } - else - { - // get a number - - unsigned int hexValue; + unsigned int hexValue; - istringstream str( m_line ); - str.seekg( m_column ); - str >> hex >> hexValue; + istringstream str( m_line ); + str.seekg( m_column ); + if (str >> hex >> hexValue) + { m_column = static_cast< size_t >( str.tellg() ); value = static_cast< double >( hexValue ); } + else + { + throw AsmException_SyntaxError_BadHex( m_line, m_column ); + } } else if ( m_column < m_line.length() && m_line[ m_column ] == '%' ) { From 2b0c19ddc58f1812e987fc2a995635f8744935be Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 21:19:56 +0100 Subject: [PATCH 084/144] Simple tests of value parsing --- test/1-values/badbin1.fail.6502 | 3 +++ test/1-values/baddec1.fail.6502 | 3 +++ test/1-values/badhex1.fail.6502 | 3 +++ test/1-values/badhex2.fail.6502 | 2 ++ test/1-values/values.6502 | 15 +++++++++++++++ 5 files changed, 26 insertions(+) create mode 100644 test/1-values/badbin1.fail.6502 create mode 100644 test/1-values/baddec1.fail.6502 create mode 100644 test/1-values/badhex1.fail.6502 create mode 100644 test/1-values/badhex2.fail.6502 create mode 100644 test/1-values/values.6502 diff --git a/test/1-values/badbin1.fail.6502 b/test/1-values/badbin1.fail.6502 new file mode 100644 index 0000000..4e18603 --- /dev/null +++ b/test/1-values/badbin1.fail.6502 @@ -0,0 +1,3 @@ +\ Binary literal with no number +A=% + diff --git a/test/1-values/baddec1.fail.6502 b/test/1-values/baddec1.fail.6502 new file mode 100644 index 0000000..5a83079 --- /dev/null +++ b/test/1-values/baddec1.fail.6502 @@ -0,0 +1,3 @@ +\ Decimal literal with no number +A=.label + diff --git a/test/1-values/badhex1.fail.6502 b/test/1-values/badhex1.fail.6502 new file mode 100644 index 0000000..b1990d3 --- /dev/null +++ b/test/1-values/badhex1.fail.6502 @@ -0,0 +1,3 @@ +\ Hex literal with no number +A=& + diff --git a/test/1-values/badhex2.fail.6502 b/test/1-values/badhex2.fail.6502 new file mode 100644 index 0000000..60b7e94 --- /dev/null +++ b/test/1-values/badhex2.fail.6502 @@ -0,0 +1,2 @@ +\ Maximum hex literal is &ffffffff +assert(&100000000=4294967296) diff --git a/test/1-values/values.6502 b/test/1-values/values.6502 new file mode 100644 index 0000000..3a9a3ea --- /dev/null +++ b/test/1-values/values.6502 @@ -0,0 +1,15 @@ +assert(&10=16) +assert($10=16) +assert(%10000=16) +assert('A'=&41) +assert(&13579BDF=324508639) +assert(&2468ACE0=610839776) +assert(&abcdef=11259375) +assert(&7fffffff=2147483647) +\ Unlike BBC BASIC, hex constants are always positive +assert(&ffffffff=4294967295) +\ But binary constants can be negative +assert(%11111111111111111111111111111111=-1) +\ Program counter +assert(*=0) + From 0368ca555aaee13f80e341462763f14061f6ff31 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 21:27:59 +0100 Subject: [PATCH 085/144] Typo of STRINGS$ in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c7fb90..3d0fd25 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ CHR$(val) Return a string with a single character with ASCII value val ASC(str) Return the ASCII value of the first character of str MID$(str,index,length) Return length characters of str starting at (one-based) index -STRINGS$(count,str) +STRING$(count,str) Return str repeated count times LOWER$(str) Return str converted to lowercase UPPER$(str) Return str converted to uppercase From d04f5fbe3d0fea7696fe7c9ef9da174721ae3548 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 22:09:32 +0100 Subject: [PATCH 086/144] LEFT$ and RIGHT$ --- README.md | 6 +++-- src/expression.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++ src/lineparser.h | 2 ++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3d0fd25..4221b88 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,8 @@ CHR$(val) Return a string with a single character with ASCII value val ASC(str) Return the ASCII value of the first character of str MID$(str,index,length) Return length characters of str starting at (one-based) index +LEFT$(str,length) Return the first length characters of str +RIGHT$(str,length) Return the last length characters of str STRING$(count,str) Return str repeated count times LOWER$(str) Return str converted to lowercase @@ -739,8 +741,8 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Added -writes, -dd and -labels options (thanks to tricky for these) Added CMake support and man page (thanks to Dave Lambley) Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, - STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with thanks to - Steven Flintham.) + LEFT$, RIGHT$, STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with + thanks to Steven Flintham.) 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT diff --git a/src/expression.cpp b/src/expression.cpp index 1f767bc..831fb2a 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -111,6 +111,8 @@ const LineParser::Operator LineParser::m_gaUnaryOperatorTable[] = { "CHR$(", 10, 1, &LineParser::EvalChr }, { "ASC(", 10, 1, &LineParser::EvalAsc }, { "MID$(", 10, 3, &LineParser::EvalMid }, + { "LEFT$(", 10, 2, &LineParser::EvalLeft }, + { "RIGHT$(", 10, 2, &LineParser::EvalRight }, { "STRING$(", 10, 2, &LineParser::EvalString }, { "UPPER$(", 10, 1, &LineParser::EvalUpper }, { "LOWER$(", 10, 1, &LineParser::EvalLower } @@ -1602,6 +1604,67 @@ void LineParser::EvalMid() } +/*************************************************************************************************/ +/** + LineParser::EvalLeft() +*/ +/*************************************************************************************************/ +void LineParser::EvalLeft() +{ + if ( m_valueStackPtr < 2 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value1 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 1 ]; + if ((value1.GetType() != Value::StringValue) || (value2.GetType() != Value::NumberValue)) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + m_valueStackPtr -= 1; + + String text = value1.GetString(); + int count = static_cast<int>(value2.GetNumber()); + if ((count < 0) || (static_cast<unsigned int>(count) > text.Length())) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + + m_valueStack[ m_valueStackPtr - 1 ] = text.SubString(0, count); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalRight() +*/ +/*************************************************************************************************/ +void LineParser::EvalRight() +{ + if ( m_valueStackPtr < 2 ) + { + throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + } + Value value1 = m_valueStack[ m_valueStackPtr - 2 ]; + Value value2 = m_valueStack[ m_valueStackPtr - 1 ]; + if ((value1.GetType() != Value::StringValue) || (value2.GetType() != Value::NumberValue)) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + m_valueStackPtr -= 1; + + String text = value1.GetString(); + int count = static_cast<int>(value2.GetNumber()); + if ((count < 0) || (static_cast<unsigned int>(count) > text.Length())) + { + throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); + } + + unsigned int ucount = static_cast<unsigned int>(count); + m_valueStack[ m_valueStackPtr - 1 ] = text.SubString(text.Length() - ucount, ucount); +} + + /*************************************************************************************************/ /** LineParser::EvalString() diff --git a/src/lineparser.h b/src/lineparser.h index 356284c..fd738d2 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -230,6 +230,8 @@ class LineParser void EvalChr(); void EvalAsc(); void EvalMid(); + void EvalLeft(); + void EvalRight(); void EvalString(); void EvalUpper(); void EvalLower(); From 4d3f41dc25b053c4b8b90a4441376aef3c532f2f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 23:00:42 +0100 Subject: [PATCH 087/144] Undefined behaviour in EvalShiftLeft and EvalShiftRight --- src/expression.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index 831fb2a..5297b7a 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -925,6 +925,28 @@ void LineParser::EvalMod() +/*************************************************************************************************/ +/** + Shifting helper functions +*/ +/*************************************************************************************************/ +static int LogicalShiftLeft(int value, unsigned int shift) +{ + return static_cast<int>(static_cast<unsigned int>(value) << shift); +} + +static int ArithmeticShiftRight(int value, unsigned int shift) +{ + unsigned int shifted = static_cast<unsigned int>(value) >> shift; + if (value < 0) + { + const unsigned int bitcount = 8 * sizeof(unsigned int); + const unsigned int ones = ~0; + shifted |= (ones << (bitcount - shift)); + } + return static_cast<int>(shifted); +} + /*************************************************************************************************/ /** LineParser::EvalShiftLeft() @@ -944,7 +966,7 @@ void LineParser::EvalShiftLeft() } else if ( shift > 0 ) { - result = val << shift; + result = LogicalShiftLeft(val, static_cast<unsigned int>(shift)); } else if ( shift == 0 ) { @@ -952,7 +974,7 @@ void LineParser::EvalShiftLeft() } else { - result = val >> (-shift); + result = ArithmeticShiftRight(val, static_cast<unsigned int>(-shift)); } m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( result ); @@ -980,7 +1002,7 @@ void LineParser::EvalShiftRight() } else if ( shift > 0 ) { - result = val >> shift; + result = ArithmeticShiftRight(val, static_cast<unsigned int>(shift)); } else if ( shift == 0 ) { @@ -988,7 +1010,7 @@ void LineParser::EvalShiftRight() } else { - result = val << (-shift); + result = LogicalShiftLeft(val, static_cast<unsigned int>(-shift)); } m_valueStack[ m_valueStackPtr - 2 ] = static_cast< double >( result ); From bb9403bbd1244a28bdbb701c90d46115c70fa28a Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 23:08:33 +0100 Subject: [PATCH 088/144] Shift tests with negative values and shifts --- test/2-expressions/operators.6502 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/2-expressions/operators.6502 b/test/2-expressions/operators.6502 index 23c6102..972040a 100644 --- a/test/2-expressions/operators.6502 +++ b/test/2-expressions/operators.6502 @@ -23,7 +23,12 @@ assert(9.9 MOD 2.9=1) \ Shifts assert(3 << 5=96) -assert(96 >> 5 = 3) +assert(96 << -5=3) +assert(96 >> 5=3) +assert(3 >> -5=96) + +assert(-1>>5=-1) +assert(&80000000>>5=-&4000000) \ Logic assert((14 AND 28)=12) From d7dd6f5a6b3cd911d0cf7f1dcb47e6f7aec1d004 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 23:26:34 +0100 Subject: [PATCH 089/144] Don't print 32-bit integers in scientific notation This also applies to STR$ --- src/commands.cpp | 3 ++- src/expression.cpp | 4 ++-- src/stringutils.cpp | 31 +++++++++++++++++++++++++++++++ src/stringutils.h | 1 + 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index d1cdb5f..b56d79e 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1458,7 +1458,8 @@ void LineParser::HandlePrint() { if (value.GetType() == Value::NumberValue) { - cout << value.GetNumber() << " "; + StringUtils::PrintNumber(cout, value.GetNumber()); + cout << " "; } else if (value.GetType() == Value::StringValue) { diff --git a/src/expression.cpp b/src/expression.cpp index 8218d1c..8c502f9 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -37,7 +37,7 @@ #include "sourcefile.h" #include "random.h" #include "constants.h" - +#include "stringutils.h" using namespace std; @@ -1469,7 +1469,7 @@ Value LineParser::FormatAssemblyTime(const char* formatString) void LineParser::EvalStr() { ostringstream stream; - stream << StackTopNumber(); + StringUtils::PrintNumber(stream, StackTopNumber()); string result = stream.str(); m_valueStack[ m_valueStackPtr - 1 ] = String(result.data(), result.length()); diff --git a/src/stringutils.cpp b/src/stringutils.cpp index cd9cc96..8d7884e 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -120,5 +120,36 @@ std::string FormattedErrorLocation( const std::string& filename, int lineNumber } +/*************************************************************************************************/ +/** + PrintNumber() + + Print a number to a stream ensuring that 32-bit ints are not written in scientific notation + + @param dest The stream to write to + @param value The number to write + +*/ +/*************************************************************************************************/ +void PrintNumber(std::ostream& dest, double value) +{ + double integer_part; + double frac = modf(value, &integer_part); + if ((frac == 0.0) && (-4294967296.0 < integer_part) && (integer_part < 4294967296.0)) + { + if (integer_part < 0) + { + dest << '-' << static_cast<unsigned int>(-integer_part); + } + else + { + dest << static_cast<unsigned int>(integer_part); + } + } + else + { + dest << value; + } +} } // namespace StringUtils diff --git a/src/stringutils.h b/src/stringutils.h index 6852a3d..263148d 100644 --- a/src/stringutils.h +++ b/src/stringutils.h @@ -31,6 +31,7 @@ namespace StringUtils void ExpandTabsToSpaces( std::string& line, size_t tabWidth ); bool EatWhitespace( const std::string& line, size_t& column ); std::string FormattedErrorLocation ( const std::string& filename, int lineNumber ); + void PrintNumber(std::ostream& dest, double value); } From ba1335e115c38c098afe2c70f74f05853950eab4 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 13 Jul 2021 23:47:33 +0100 Subject: [PATCH 090/144] Comment explaining local-forward-branch-3 test --- test/4-assembler/local-forward-branch-3.6502 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/4-assembler/local-forward-branch-3.6502 b/test/4-assembler/local-forward-branch-3.6502 index 758f8b1..1aaa2b2 100644 --- a/test/4-assembler/local-forward-branch-3.6502 +++ b/test/4-assembler/local-forward-branch-3.6502 @@ -1,5 +1,10 @@ org &2000 +\ This is a test case for an old bug as described here +\ http://www.retrosoftware.co.uk/forum/viewtopic.php?f=17&t=994&p=7797#p7797 +\ This diff shows the fix +\ https://github.com/ZornsLemma/beebasm/compare/save-high-order...ZornsLemma:scoped-label-fix-2 + a = &70 macro foo zp From 709d3a12f69faf311f651666dd75d3fd292c287c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 00:16:30 +0100 Subject: [PATCH 091/144] Removed use of fmod when converting values to strings --- src/stringutils.cpp | 25 ++++++++++++++----------- test/2-expressions/stringfunctions.6502 | 2 ++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/stringutils.cpp b/src/stringutils.cpp index 8d7884e..5ad19f2 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -133,23 +133,26 @@ std::string FormattedErrorLocation( const std::string& filename, int lineNumber /*************************************************************************************************/ void PrintNumber(std::ostream& dest, double value) { - double integer_part; - double frac = modf(value, &integer_part); - if ((frac == 0.0) && (-4294967296.0 < integer_part) && (integer_part < 4294967296.0)) + if ((-4294967296.0 < value) && (value < -0.5)) { - if (integer_part < 0) + unsigned int abs_part = static_cast<unsigned int>(-value); + if (-static_cast<double>(abs_part) == value) { - dest << '-' << static_cast<unsigned int>(-integer_part); - } - else - { - dest << static_cast<unsigned int>(integer_part); + dest << '-' << abs_part; + return; } } - else + else if ((0.0 <= value) && (value < 4294967296.0)) { - dest << value; + unsigned int abs_part = static_cast<unsigned int>(value); + if (static_cast<double>(abs_part) == value) + { + dest << abs_part; + return; + } } + + dest << value; } } // namespace StringUtils diff --git a/test/2-expressions/stringfunctions.6502 b/test/2-expressions/stringfunctions.6502 index b08eca0..e79ed65 100644 --- a/test/2-expressions/stringfunctions.6502 +++ b/test/2-expressions/stringfunctions.6502 @@ -16,6 +16,8 @@ ASSERT VAL("7.2") = 7.2 ASSERT EVAL(foo) = "fooled!" ASSERT STR$(7.2) = "7.2" ASSERT STR$~(27.3) = "1B" +ASSERT STR$(&FFFFFFFF) = "4294967295" +ASSERT STR$(-&FFFFFFFF) = "-4294967295" ASSERT LEN("") = 0 ASSERT LEN(foo) = 3 ASSERT LEN("a""b") = 3 From 95dd0f8a1083b18022cc1cbb16d52dd57dee0c95 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 00:47:21 +0100 Subject: [PATCH 092/144] Binary literals - always positive, validate length --- src/expression.cpp | 34 +++++++++++++++++++-------------- test/1-values/badbin2.fail.6502 | 3 +++ test/1-values/values.6502 | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 test/1-values/badbin2.fail.6502 diff --git a/src/expression.cpp b/src/expression.cpp index 8c502f9..c1da799 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -178,28 +178,34 @@ Value LineParser::GetValue() { // get binary + // skip the number prefix m_column++; - if ( m_column >= m_line.length() || ( m_line[ m_column ] != '0' && m_line[ m_column ] != '1' ) ) + int start_column = m_column; + unsigned int binValue = 0; + + // Skip leading zeroes + while ( m_column < m_line.length() && m_line[ m_column ] == '0' ) { - // badly formed bin literal - throw AsmException_SyntaxError_BadBin( m_line, m_column ); + m_column++; } - else - { - // parse binary number - int binValue = 0; + // Remember the column containing the first one (if any) + int first_one = m_column; - do - { - binValue = ( binValue * 2 ) + ( m_line[ m_column ] - '0' ); - m_column++; - } - while ( m_column < m_line.length() && ( m_line[ m_column ] == '0' || m_line[ m_column ] == '1' ) ); + while ( m_column < m_line.length() && ( m_line[ m_column ] == '0' || m_line[ m_column ] == '1' ) ) + { + binValue = ( binValue * 2 ) + ( m_line[ m_column ] - '0' ); + m_column++; + } - value = static_cast< double >( binValue ); + if ( m_column == start_column || m_column - first_one > 32 ) + { + // badly formed bin literal + throw AsmException_SyntaxError_BadBin( m_line, m_column ); } + + value = static_cast< double >( binValue ); } else if ( m_column < m_line.length() && m_line[ m_column ] == '*' ) { diff --git a/test/1-values/badbin2.fail.6502 b/test/1-values/badbin2.fail.6502 new file mode 100644 index 0000000..0e01529 --- /dev/null +++ b/test/1-values/badbin2.fail.6502 @@ -0,0 +1,3 @@ +\ Maximum binary literal is %11111111111111111111111111111111 +assert(%100000000000000000000000000000000=4294967296) + diff --git a/test/1-values/values.6502 b/test/1-values/values.6502 index 3a9a3ea..e529bc3 100644 --- a/test/1-values/values.6502 +++ b/test/1-values/values.6502 @@ -9,7 +9,7 @@ assert(&7fffffff=2147483647) \ Unlike BBC BASIC, hex constants are always positive assert(&ffffffff=4294967295) \ But binary constants can be negative -assert(%11111111111111111111111111111111=-1) +assert(%000011111111111111111111111111111111=&ffffffff) \ Program counter assert(*=0) From c4767a8c7562f7b839f510bc8d67fbf0925772f1 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 14:32:23 +0100 Subject: [PATCH 093/144] Binary literals - fix signed/unsigned mismatch --- src/expression.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/expression.cpp b/src/expression.cpp index c1da799..34ea321 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -181,7 +181,7 @@ Value LineParser::GetValue() // skip the number prefix m_column++; - int start_column = m_column; + size_t start_column = m_column; unsigned int binValue = 0; // Skip leading zeroes @@ -191,7 +191,7 @@ Value LineParser::GetValue() } // Remember the column containing the first one (if any) - int first_one = m_column; + size_t first_one = m_column; while ( m_column < m_line.length() && ( m_line[ m_column ] == '0' || m_line[ m_column ] == '1' ) ) { From 5ad3d0aa2d6d91f7dec9b06caecd5e261882eb50 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 16:00:01 +0100 Subject: [PATCH 094/144] Fixed comment in binary constant test --- test/1-values/values.6502 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/1-values/values.6502 b/test/1-values/values.6502 index e529bc3..0e2ee0c 100644 --- a/test/1-values/values.6502 +++ b/test/1-values/values.6502 @@ -8,7 +8,7 @@ assert(&abcdef=11259375) assert(&7fffffff=2147483647) \ Unlike BBC BASIC, hex constants are always positive assert(&ffffffff=4294967295) -\ But binary constants can be negative +\ And so are binary constants now assert(%000011111111111111111111111111111111=&ffffffff) \ Program counter assert(*=0) From eb847f287119ab7d862ac23a868ea14c6de50a4d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 16:00:55 +0100 Subject: [PATCH 095/144] Tests for all opcodes/addressing modes --- test/4-assembler/all6502.6502 | 320 +++++++++++++++++++++++++ test/4-assembler/all6502.gold.ssd | Bin 0 -> 1024 bytes test/4-assembler/all65C02.6502 | 372 +++++++++++++++++++++++++++++ test/4-assembler/all65C02.gold.ssd | Bin 0 -> 1024 bytes 4 files changed, 692 insertions(+) create mode 100644 test/4-assembler/all6502.6502 create mode 100644 test/4-assembler/all6502.gold.ssd create mode 100644 test/4-assembler/all65C02.6502 create mode 100644 test/4-assembler/all65C02.gold.ssd diff --git a/test/4-assembler/all6502.6502 b/test/4-assembler/all6502.6502 new file mode 100644 index 0000000..8156635 --- /dev/null +++ b/test/4-assembler/all6502.6502 @@ -0,0 +1,320 @@ + +\ Every valid 6502 opcode. + +\ This file was generated once from the opcode data in beebasm and +\ once from the data in another tool, and the files were identical. +\ Additionally, this was assembled with beebasm, disassembled and +\ successfully compared to this source. + +\ The gold ssd ensures nothing has changed since. + +ORG &2000 + +.start + +\ &00 +BRK +\ &01 +ORA (&12,X) +\ &05 +ORA &12 +\ &06 +ASL &12 +\ &08 +PHP +\ &09 +ORA #&12 +\ &0A +ASL A +\ &0D +ORA &1234 +\ &0E +ASL &1234 +\ &10 +BPL P%+2 +\ &11 +ORA (&12),Y +\ &15 +ORA &12,X +\ &16 +ASL &12,X +\ &18 +CLC +\ &19 +ORA &1234,Y +\ &1D +ORA &1234,X +\ &1E +ASL &1234,X +\ &20 +JSR &1234 +\ &21 +AND (&12,X) +\ &24 +BIT &12 +\ &25 +AND &12 +\ &26 +ROL &12 +\ &28 +PLP +\ &29 +AND #&12 +\ &2A +ROL A +\ &2C +BIT &1234 +\ &2D +AND &1234 +\ &2E +ROL &1234 +\ &30 +BMI P%+2 +\ &31 +AND (&12),Y +\ &35 +AND &12,X +\ &36 +ROL &12,X +\ &38 +SEC +\ &39 +AND &1234,Y +\ &3D +AND &1234,X +\ &3E +ROL &1234,X +\ &40 +RTI +\ &41 +EOR (&12,X) +\ &45 +EOR &12 +\ &46 +LSR &12 +\ &48 +PHA +\ &49 +EOR #&12 +\ &4A +LSR A +\ &4C +JMP &1234 +\ &4D +EOR &1234 +\ &4E +LSR &1234 +\ &50 +BVC P%+2 +\ &51 +EOR (&12),Y +\ &55 +EOR &12,X +\ &56 +LSR &12,X +\ &58 +CLI +\ &59 +EOR &1234,Y +\ &5D +EOR &1234,X +\ &5E +LSR &1234,X +\ &60 +RTS +\ &61 +ADC (&12,X) +\ &65 +ADC &12 +\ &66 +ROR &12 +\ &68 +PLA +\ &69 +ADC #&12 +\ &6A +ROR A +\ &6C +JMP (&1234) +\ &6D +ADC &1234 +\ &6E +ROR &1234 +\ &70 +BVS P%+2 +\ &71 +ADC (&12),Y +\ &75 +ADC &12,X +\ &76 +ROR &12,X +\ &78 +SEI +\ &79 +ADC &1234,Y +\ &7D +ADC &1234,X +\ &7E +ROR &1234,X +\ &81 +STA (&12,X) +\ &84 +STY &12 +\ &85 +STA &12 +\ &86 +STX &12 +\ &88 +DEY +\ &8A +TXA +\ &8C +STY &1234 +\ &8D +STA &1234 +\ &8E +STX &1234 +\ &90 +BCC P%+2 +\ &91 +STA (&12),Y +\ &94 +STY &12,X +\ &95 +STA &12,X +\ &96 +STX &12,Y +\ &98 +TYA +\ &99 +STA &1234,Y +\ &9A +TXS +\ &9D +STA &1234,X +\ &A0 +LDY #&12 +\ &A1 +LDA (&12,X) +\ &A2 +LDX #&12 +\ &A4 +LDY &12 +\ &A5 +LDA &12 +\ &A6 +LDX &12 +\ &A8 +TAY +\ &A9 +LDA #&12 +\ &AA +TAX +\ &AC +LDY &1234 +\ &AD +LDA &1234 +\ &AE +LDX &1234 +\ &B0 +BCS P%+2 +\ &B1 +LDA (&12),Y +\ &B4 +LDY &12,X +\ &B5 +LDA &12,X +\ &B6 +LDX &12,Y +\ &B8 +CLV +\ &B9 +LDA &1234,Y +\ &BA +TSX +\ &BC +LDY &1234,X +\ &BD +LDA &1234,X +\ &BE +LDX &1234,Y +\ &C0 +CPY #&12 +\ &C1 +CMP (&12,X) +\ &C4 +CPY &12 +\ &C5 +CMP &12 +\ &C6 +DEC &12 +\ &C8 +INY +\ &C9 +CMP #&12 +\ &CA +DEX +\ &CC +CPY &1234 +\ &CD +CMP &1234 +\ &CE +DEC &1234 +\ &D0 +BNE P%+2 +\ &D1 +CMP (&12),Y +\ &D5 +CMP &12,X +\ &D6 +DEC &12,X +\ &D8 +CLD +\ &D9 +CMP &1234,Y +\ &DD +CMP &1234,X +\ &DE +DEC &1234,X +\ &E0 +CPX #&12 +\ &E1 +SBC (&12,X) +\ &E4 +CPX &12 +\ &E5 +SBC &12 +\ &E6 +INC &12 +\ &E8 +INX +\ &E9 +SBC #&12 +\ &EA +NOP +\ &EC +CPX &1234 +\ &ED +SBC &1234 +\ &EE +INC &1234 +\ &F0 +BEQ P%+2 +\ &F1 +SBC (&12),Y +\ &F5 +SBC &12,X +\ &F6 +INC &12,X +\ &F8 +SED +\ &F9 +SBC &1234,Y +\ &FD +SBC &1234,X +\ &FE +INC &1234,X + +.end + +SAVE "test", start, end diff --git a/test/4-assembler/all6502.gold.ssd b/test/4-assembler/all6502.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..9f4d83d103b5b137ed6a004b1adc07576f154563 GIT binary patch literal 1024 zcmd^-!7JWz0LH(sMXZQV5eNFMUSgrChd5Z?!!)wRf$U&$uvpt7FL9t0OJY$7rC27! zq7Xt>CKHPTaiFcxEW}~519^*oLZ0XF^f`X&G3F-D{1T^SS@v7JU*q|}GOjvo#_fKI zyKHT^<AJ9a550wt@HKw;6F^{)2o@p25|6MGN*Li0JdPAmB3e8#F~kx_e1b?6Peqc0 zXC(7Hh14|hLcA2|CWB0}$bKbq#A}h8M?P;TDCF%s@m_ooMW&ci$|$c8m7+>iQ$sBu zsjF`gjpCDNGR?HmN*nDRqO(hM)5B+a`9dH40}Kv{VewUrm{G<UXM)KoF+C$@#hjSu z8w-4Ak)<CjuZUIgQ>>YFHrQlqTkMEk@r&Q=vCjd24*7c|j>U;MHD~<eoQwbBQe25U Gewb@_TVW#r literal 0 HcmV?d00001 diff --git a/test/4-assembler/all65C02.6502 b/test/4-assembler/all65C02.6502 new file mode 100644 index 0000000..c9b9c24 --- /dev/null +++ b/test/4-assembler/all65C02.6502 @@ -0,0 +1,372 @@ + +\ Every valid 65C02 opcode. + +\ This is not thoroughly rested, but it is proof against anything changing. + +ORG &2000 + +.start + +CPU 1 + +\ &00 +BRK +\ &01 +ORA (&12,X) +\ &04 +TSB &12 +\ &05 +ORA &12 +\ &06 +ASL &12 +\ &08 +PHP +\ &09 +ORA #&12 +\ &0A +ASL A +\ &0C +TSB &1234 +\ &0D +ORA &1234 +\ &0E +ASL &1234 +\ &10 +BPL P%+2 +\ &11 +ORA (&12),Y +\ &12 +ORA (&12) +\ &14 +TRB &12 +\ &15 +ORA &12,X +\ &16 +ASL &12,X +\ &18 +CLC +\ &19 +ORA &1234,Y +\ &1A +INC A +\ &1C +TRB &1234 +\ &1D +ORA &1234,X +\ &1E +ASL &1234,X +\ &20 +JSR &1234 +\ &21 +AND (&12,X) +\ &24 +BIT &12 +\ &25 +AND &12 +\ &26 +ROL &12 +\ &28 +PLP +\ &29 +AND #&12 +\ &2A +ROL A +\ &2C +BIT &1234 +\ &2D +AND &1234 +\ &2E +ROL &1234 +\ &30 +BMI P%+2 +\ &31 +AND (&12),Y +\ &32 +AND (&12) +\ &34 +BIT &12,X +\ &35 +AND &12,X +\ &36 +ROL &12,X +\ &38 +SEC +\ &39 +AND &1234,Y +\ &3A +DEC A +\ &3C +BIT &1234,X +\ &3D +AND &1234,X +\ &3E +ROL &1234,X +\ &40 +RTI +\ &41 +EOR (&12,X) +\ &45 +EOR &12 +\ &46 +LSR &12 +\ &48 +PHA +\ &49 +EOR #&12 +\ &4A +LSR A +\ &4C +JMP &1234 +\ &4D +EOR &1234 +\ &4E +LSR &1234 +\ &50 +BVC P%+2 +\ &51 +EOR (&12),Y +\ &52 +EOR (&12) +\ &55 +EOR &12,X +\ &56 +LSR &12,X +\ &58 +CLI +\ &59 +EOR &1234,Y +\ &5A +PHY +\ &5D +EOR &1234,X +\ &5E +LSR &1234,X +\ &60 +RTS +\ &61 +ADC (&12,X) +\ &64 +STZ &12 +\ &65 +ADC &12 +\ &66 +ROR &12 +\ &68 +PLA +\ &69 +ADC #&12 +\ &6A +ROR A +\ &6C +JMP (&1234) +\ &6D +ADC &1234 +\ &6E +ROR &1234 +\ &70 +BVS P%+2 +\ &71 +ADC (&12),Y +\ &72 +ADC (&12) +\ &74 +STZ &12,X +\ &75 +ADC &12,X +\ &76 +ROR &12,X +\ &78 +SEI +\ &79 +ADC &1234,Y +\ &7A +PLY +\ &7C +JMP (&1234,X) +\ &7D +ADC &1234,X +\ &7E +ROR &1234,X +\ &80 +BRA P%+2 +\ &81 +STA (&12,X) +\ &84 +STY &12 +\ &85 +STA &12 +\ &86 +STX &12 +\ &88 +DEY +\ &89 +BIT #&12 +\ &8A +TXA +\ &8C +STY &1234 +\ &8D +STA &1234 +\ &8E +STX &1234 +\ &90 +BCC P%+2 +\ &91 +STA (&12),Y +\ &92 +STA (&12) +\ &94 +STY &12,X +\ &95 +STA &12,X +\ &96 +STX &12,Y +\ &98 +TYA +\ &99 +STA &1234,Y +\ &9A +TXS +\ &9C +STZ &1234 +\ &9D +STA &1234,X +\ &9E +STZ &1234,X +\ &A0 +LDY #&12 +\ &A1 +LDA (&12,X) +\ &A2 +LDX #&12 +\ &A4 +LDY &12 +\ &A5 +LDA &12 +\ &A6 +LDX &12 +\ &A8 +TAY +\ &A9 +LDA #&12 +\ &AA +TAX +\ &AC +LDY &1234 +\ &AD +LDA &1234 +\ &AE +LDX &1234 +\ &B0 +BCS P%+2 +\ &B1 +LDA (&12),Y +\ &B2 +LDA (&12) +\ &B4 +LDY &12,X +\ &B5 +LDA &12,X +\ &B6 +LDX &12,Y +\ &B8 +CLV +\ &B9 +LDA &1234,Y +\ &BA +TSX +\ &BC +LDY &1234,X +\ &BD +LDA &1234,X +\ &BE +LDX &1234,Y +\ &C0 +CPY #&12 +\ &C1 +CMP (&12,X) +\ &C4 +CPY &12 +\ &C5 +CMP &12 +\ &C6 +DEC &12 +\ &C8 +INY +\ &C9 +CMP #&12 +\ &CA +DEX +\ &CC +CPY &1234 +\ &CD +CMP &1234 +\ &CE +DEC &1234 +\ &D0 +BNE P%+2 +\ &D1 +CMP (&12),Y +\ &D2 +CMP (&12) +\ &D5 +CMP &12,X +\ &D6 +DEC &12,X +\ &D8 +CLD +\ &D9 +CMP &1234,Y +\ &DA +PHX +\ &DD +CMP &1234,X +\ &DE +DEC &1234,X +\ &E0 +CPX #&12 +\ &E1 +SBC (&12,X) +\ &E4 +CPX &12 +\ &E5 +SBC &12 +\ &E6 +INC &12 +\ &E8 +INX +\ &E9 +SBC #&12 +\ &EA +NOP +\ &EC +CPX &1234 +\ &ED +SBC &1234 +\ &EE +INC &1234 +\ &F0 +BEQ P%+2 +\ &F1 +SBC (&12),Y +\ &F2 +SBC (&12) +\ &F5 +SBC &12,X +\ &F6 +INC &12,X +\ &F8 +SED +\ &F9 +SBC &1234,Y +\ &FA +PLX +\ &FD +SBC &1234,X +\ &FE +INC &1234,X + +.end + +SAVE "test", start, end + diff --git a/test/4-assembler/all65C02.gold.ssd b/test/4-assembler/all65C02.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..f9d5dcae4039a4a0e08f44802bd77268a29eeb97 GIT binary patch literal 1024 zcmd^-(JS6z0EORgY@u!OG|LwHDKy&Ftg|(1(_9c5nVMKxTa7j$+rDiGO`#E*HJW9M zWoyKmWg2awMzdI^Xw(*E8jV<OjiM?0Bb0M4PS532$C&llldsz?%kuq?n`@jLEVI^I zHnU}OiLcq(aAYgnaB{|FyVxOi3RmL>?82Shc<d2-J%yL>7MAeA7eD;j7eL^CaX=gt zhfENM5l1-6v0!ohgg7Ze#Hmoi2qz+vsM8`^#E4jPhB(d=PeP(Nmn6=M3m3UWGAUfX zLTZ{w7a1bcT;&>eZg7*VY>|^I@<hJ4Z3-x)h+^)PP+BI+MTNL)D!Ipfs;K5cjd)lq z9*M`Ij(VQZz*C;_ypg76(IQ@mm!_3hwDFpD-gJo0x8j}X65aIho?iO+(9g#KF(^KX yA@iAGMi}MGm>B;mzKIDYnc_Rs{Fq_(r<fD-V!<r(i{Jd=?~+(v5vyXu-0UCB{%=eG literal 0 HcmV?d00001 From 6f47f7c0dc80f8a4014678e3dc650e5b9f707fc8 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 16:34:32 +0100 Subject: [PATCH 096/144] Test for recovery from undefined value error in list --- test/4-assembler/expressionrecovery.6502 | 24 +++++++++++++++++++ test/4-assembler/expressionrecovery.gold.ssd | Bin 0 -> 768 bytes 2 files changed, 24 insertions(+) create mode 100644 test/4-assembler/expressionrecovery.6502 create mode 100644 test/4-assembler/expressionrecovery.gold.ssd diff --git a/test/4-assembler/expressionrecovery.6502 b/test/4-assembler/expressionrecovery.6502 new file mode 100644 index 0000000..cabcc52 --- /dev/null +++ b/test/4-assembler/expressionrecovery.6502 @@ -0,0 +1,24 @@ +\ In a list of parameters check that the parser skips to the +\ next parameter after an error, not to the end of the list. + +\ This error only revealed itself when the list contained +\ a function call. + +\ If this test fails then beebasm will report that the second +\ pass has generated different code to the first. + +ORG &2000 + +.start + +EQUB LO(undef1), HI(undef1), 5, 6 +.undef1 +EQUW LO(undef1), HI(undef1), undef2, 512 +.undef2 +EQUD undef2, HI(undef3), &20000, -1 +.undef3 + +.end + +SAVE "test", start, end + diff --git a/test/4-assembler/expressionrecovery.gold.ssd b/test/4-assembler/expressionrecovery.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..00578717b12ddb15339b984f8a420435226caf30 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFf!@$5avOLbfz@os)#sc&_j{*Y|5Cd5tz{Kz$ K2*Au?P5=NSaR-?I literal 0 HcmV?d00001 From 497d40310af82ce489b493f61c0ff94466e9ba4c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 17:21:12 +0100 Subject: [PATCH 097/144] Tests for the various syntax permutations of SAVE --- test/3-directives/save/savename.6502 | 27 +++++++++++++++++++ test/3-directives/save/savename.gold.ssd | Bin 0 -> 1280 bytes test/3-directives/save/savenoend.fail.6502 | 11 ++++++++ test/3-directives/save/savenoname.fail.6502 | 11 ++++++++ test/3-directives/save/savenoname1.6502 | 13 +++++++++ test/3-directives/save/savenoname1.gold.ssd | Bin 0 -> 768 bytes test/3-directives/save/savenoname2.6502 | 14 ++++++++++ test/3-directives/save/savenoname2.gold.ssd | Bin 0 -> 768 bytes test/3-directives/save/savenoname3.6502 | 16 +++++++++++ test/3-directives/save/savenoname3.gold.ssd | Bin 0 -> 768 bytes test/3-directives/save/savenostart.fail.6502 | 11 ++++++++ 11 files changed, 103 insertions(+) create mode 100644 test/3-directives/save/savename.6502 create mode 100644 test/3-directives/save/savename.gold.ssd create mode 100644 test/3-directives/save/savenoend.fail.6502 create mode 100644 test/3-directives/save/savenoname.fail.6502 create mode 100644 test/3-directives/save/savenoname1.6502 create mode 100644 test/3-directives/save/savenoname1.gold.ssd create mode 100644 test/3-directives/save/savenoname2.6502 create mode 100644 test/3-directives/save/savenoname2.gold.ssd create mode 100644 test/3-directives/save/savenoname3.6502 create mode 100644 test/3-directives/save/savenoname3.gold.ssd create mode 100644 test/3-directives/save/savenostart.fail.6502 diff --git a/test/3-directives/save/savename.6502 b/test/3-directives/save/savename.6502 new file mode 100644 index 0000000..a8afa05 --- /dev/null +++ b/test/3-directives/save/savename.6502 @@ -0,0 +1,27 @@ +\ beebasm -o test + +\ The "test" specified above should be ignored + +ORG &2000 + +.start + +.f1 +LDA #'C' +JMP &FFEE + +.f2 +LDA #'T' +JMP &FFEE + +.f3 +LDA #'R' +JMP &FFEE + +.end + +SAVE "test1", f1, f2 +SAVE "test2", f1, f3, f2 +base = &3000 +SAVE "test3", f1, end, base + f3 - start, base + diff --git a/test/3-directives/save/savename.gold.ssd b/test/3-directives/save/savename.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..a65a7c72234de93f775681cd854db1652f4d72d3 GIT binary patch literal 1280 zcmZQzfPj+J;u2#81r-o&1f>muG*o(cQxePy3<g{V{0s~%3<|6YTnr4%3<^LN0|V3W i_BaE>N@t&U|A)IL@%Rty|CJ#lHV_d0Ul}wK0|5XB?i*tO literal 0 HcmV?d00001 diff --git a/test/3-directives/save/savenoend.fail.6502 b/test/3-directives/save/savenoend.fail.6502 new file mode 100644 index 0000000..8acb958 --- /dev/null +++ b/test/3-directives/save/savenoend.fail.6502 @@ -0,0 +1,11 @@ +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +.end + +SAVE "test", start + diff --git a/test/3-directives/save/savenoname.fail.6502 b/test/3-directives/save/savenoname.fail.6502 new file mode 100644 index 0000000..2378150 --- /dev/null +++ b/test/3-directives/save/savenoname.fail.6502 @@ -0,0 +1,11 @@ +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +.end + +SAVE start, end + diff --git a/test/3-directives/save/savenoname1.6502 b/test/3-directives/save/savenoname1.6502 new file mode 100644 index 0000000..7064265 --- /dev/null +++ b/test/3-directives/save/savenoname1.6502 @@ -0,0 +1,13 @@ +\ beebasm -o test + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +.end + +SAVE start, end + diff --git a/test/3-directives/save/savenoname1.gold.ssd b/test/3-directives/save/savenoname1.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..f175f0df08a1c4b280c80d6540f3ed493599d528 GIT binary patch literal 768 wcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV%D})hvOLbfu+rJ*-T#s1X(ayw0JUBP+yDRo literal 0 HcmV?d00001 diff --git a/test/3-directives/save/savenoname2.6502 b/test/3-directives/save/savenoname2.6502 new file mode 100644 index 0000000..8b9d5e5 --- /dev/null +++ b/test/3-directives/save/savenoname2.6502 @@ -0,0 +1,14 @@ +\ beebasm -o test + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +RTS +.end + +SAVE start, end, end - 1 + diff --git a/test/3-directives/save/savenoname2.gold.ssd b/test/3-directives/save/savenoname2.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..dc3c687977576cf15a90c9f3a7ccdedaffa38e5c GIT binary patch literal 768 ycmZQzfPj+J;t~Y~1r;cFWKkT<3JeOY3TzAvOe4$V3=Ausect^~7-_yn_!j^lPX<B& literal 0 HcmV?d00001 diff --git a/test/3-directives/save/savenoname3.6502 b/test/3-directives/save/savenoname3.6502 new file mode 100644 index 0000000..999687e --- /dev/null +++ b/test/3-directives/save/savenoname3.6502 @@ -0,0 +1,16 @@ +\ beebasm -o test + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +RTS +.end + +base=&3000 + +SAVE start, end, base + end - start - 1, base + diff --git a/test/3-directives/save/savenoname3.gold.ssd b/test/3-directives/save/savenoname3.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..c852bf12716138944227ef2eb13e4c4eac95df17 GIT binary patch literal 768 ycmZQzfPj+J;t~Y~1r;cFWKkT<3JeCU25bxrOe4$V3=Ausect^~7-_yn_!j_P4+dfY literal 0 HcmV?d00001 diff --git a/test/3-directives/save/savenostart.fail.6502 b/test/3-directives/save/savenostart.fail.6502 new file mode 100644 index 0000000..9970206 --- /dev/null +++ b/test/3-directives/save/savenostart.fail.6502 @@ -0,0 +1,11 @@ +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +.end + +SAVE "test" + From d4f68cf76236796817a068c5d0918ad19c0eca9d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 17:24:44 +0100 Subject: [PATCH 098/144] SAVE parser - missing EOL check in test for comma --- src/commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.cpp b/src/commands.cpp index d1cdb5f..fc402e9 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1018,7 +1018,7 @@ void LineParser::HandleSave() { saveFile = string(saveOrStart.GetString().Text(), saveOrStart.GetString().Length()); - if ( m_line[ m_column ] != ',' ) + if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) { // did not find a comma throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); From 8fdf766af08fd80fad72516116a250da71da5c10 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 17:24:44 +0100 Subject: [PATCH 099/144] SAVE parser - missing EOL check in test for comma --- src/commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.cpp b/src/commands.cpp index b56d79e..363f795 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1018,7 +1018,7 @@ void LineParser::HandleSave() { saveFile = string(saveOrStart.GetString().Text(), saveOrStart.GetString().Length()); - if ( m_line[ m_column ] != ',' ) + if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) { // did not find a comma throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); From 23461b434472bbe5663363992c0be340360c030b Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 19:04:41 +0100 Subject: [PATCH 100/144] GetTokenAndAdvanceColumn - missing EOL check --- src/commands.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 363f795..464b31a 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -104,27 +104,32 @@ const LineParser::Token LineParser::m_gaTokenTable[] = /*************************************************************************************************/ int LineParser::GetTokenAndAdvanceColumn() { + size_t remaining = m_line.length() - m_column; + for ( int i = 0; i < static_cast<int>( sizeof m_gaTokenTable / sizeof( Token ) ); i++ ) { const char* token = m_gaTokenTable[ i ].m_pName; size_t len = strlen( token ); - // see if token matches - - bool bMatch = true; - for ( unsigned int j = 0; j < len; j++ ) + if (len <= remaining) { - if ( token[ j ] != toupper( m_line[ m_column + j ] ) ) + // see if token matches + + bool bMatch = true; + for ( unsigned int j = 0; j < len; j++ ) { - bMatch = false; - break; + if ( token[ j ] != toupper( m_line[ m_column + j ] ) ) + { + bMatch = false; + break; + } } - } - if ( bMatch ) - { - m_column += len; - return i; + if ( bMatch ) + { + m_column += len; + return i; + } } } From 57b982c13920be5a0e2777b845ba73443effae7c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 19:09:41 +0100 Subject: [PATCH 101/144] SKIP tests --- test/3-directives/skip/skip.6502 | 16 ++++++++++++++++ test/3-directives/skip/skip.gold.ssd | Bin 0 -> 8448 bytes test/3-directives/skip/skipmuch.6502 | 2 ++ test/3-directives/skip/skipnegative.fail.6502 | 2 ++ test/3-directives/skip/skipnoexpr.fail.6502 | 2 ++ test/3-directives/skip/skiptoomuch.fail.6502 | 2 ++ 6 files changed, 24 insertions(+) create mode 100644 test/3-directives/skip/skip.6502 create mode 100644 test/3-directives/skip/skip.gold.ssd create mode 100644 test/3-directives/skip/skipmuch.6502 create mode 100644 test/3-directives/skip/skipnegative.fail.6502 create mode 100644 test/3-directives/skip/skipnoexpr.fail.6502 create mode 100644 test/3-directives/skip/skiptoomuch.fail.6502 diff --git a/test/3-directives/skip/skip.6502 b/test/3-directives/skip/skip.6502 new file mode 100644 index 0000000..077e2b5 --- /dev/null +++ b/test/3-directives/skip/skip.6502 @@ -0,0 +1,16 @@ +\ Various SKIPs + +ORG &2000 + +.start + +FOR s, 0, 300, 6 + EQUW s + .block + SKIP s + ASSERT(P%=block+s) +NEXT + +.end + +SAVE "test", start, end diff --git a/test/3-directives/skip/skip.gold.ssd b/test/3-directives/skip/skip.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..d0c1d6f75c341495a39007f47e3743f48d21a254 GIT binary patch literal 8448 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgH#A;-WpvOEs*EgRI!JSbilLg7I;5?I6_LUK4| zAQHqSD=h-1AWIt{bBJP_5v2-KkqxF02_jA;sKuq!1D6EJ;(jFSz^@?$zciV$QDo{P zMq2_giYQW(Mv-pfHRlkolnN?~s9*-EMpTfhfokgOsAdv*2DOl<jmo;ZsB9i3#!a9^ zGfnhPqlu}M8#;$_BWP&CA{v@aEu&XZ%OIMYvySFAP}hPj)HRMyrtPAWJ+!dq04)rq ztC`2>Y8&k=J3~982f*Y@17Ihut-L{N3kJ;mdjn=OeJp-LA8Q8KmRAF8KRvDgKu^mC zg<anUg)8Xqh(GkVauC_b#5jl?GeAz^7!3nx$n%Yc1WFnd84VAV@PKfnMneTls6d1i cM#BbY*gz!IMni}|2tlNEM#G7ya2nc*007t-0ssI2 literal 0 HcmV?d00001 diff --git a/test/3-directives/skip/skipmuch.6502 b/test/3-directives/skip/skipmuch.6502 new file mode 100644 index 0000000..faf3628 --- /dev/null +++ b/test/3-directives/skip/skipmuch.6502 @@ -0,0 +1,2 @@ +\ SKIP as much as possible +SKIP &10000 diff --git a/test/3-directives/skip/skipnegative.fail.6502 b/test/3-directives/skip/skipnegative.fail.6502 new file mode 100644 index 0000000..720b148 --- /dev/null +++ b/test/3-directives/skip/skipnegative.fail.6502 @@ -0,0 +1,2 @@ +\ SKIP backwards is not allowed +SKIP -1 diff --git a/test/3-directives/skip/skipnoexpr.fail.6502 b/test/3-directives/skip/skipnoexpr.fail.6502 new file mode 100644 index 0000000..2781644 --- /dev/null +++ b/test/3-directives/skip/skipnoexpr.fail.6502 @@ -0,0 +1,2 @@ +\ SKIP with no expression +SKIP diff --git a/test/3-directives/skip/skiptoomuch.fail.6502 b/test/3-directives/skip/skiptoomuch.fail.6502 new file mode 100644 index 0000000..813dc72 --- /dev/null +++ b/test/3-directives/skip/skiptoomuch.fail.6502 @@ -0,0 +1,2 @@ +\ SKIP too much +SKIP &10001 From 0e183c8091ab3df5cb006ab862f12c7890f0a63d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 19:54:09 +0100 Subject: [PATCH 102/144] Typo in all65C02.6502 --- test/4-assembler/all65C02.6502 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/4-assembler/all65C02.6502 b/test/4-assembler/all65C02.6502 index c9b9c24..cd96367 100644 --- a/test/4-assembler/all65C02.6502 +++ b/test/4-assembler/all65C02.6502 @@ -1,7 +1,7 @@ \ Every valid 65C02 opcode. -\ This is not thoroughly rested, but it is proof against anything changing. +\ This is not thoroughly tested, but it is proof against anything changing. ORG &2000 From 91650f819895c170e25326a1111120ef04bd3373 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 19:58:19 +0100 Subject: [PATCH 103/144] Additional SKIP test --- test/3-directives/skip/skipextra.fail.6502 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/3-directives/skip/skipextra.fail.6502 diff --git a/test/3-directives/skip/skipextra.fail.6502 b/test/3-directives/skip/skipextra.fail.6502 new file mode 100644 index 0000000..7a503e2 --- /dev/null +++ b/test/3-directives/skip/skipextra.fail.6502 @@ -0,0 +1,2 @@ +\ Unterminated SKIP +SKIP 5, From 1d560ace3c46ca27053f9bfe8b406a1d68c62503 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 20:17:20 +0100 Subject: [PATCH 104/144] SKIPTO tests --- test/3-directives/skipto/skipto.6502 | 16 ++++++++++++++++ test/3-directives/skipto/skipto.gold.ssd | Bin 0 -> 8448 bytes test/3-directives/skipto/skiptoback.fail.6502 | 3 +++ test/3-directives/skipto/skiptoextra.fail.6502 | 2 ++ test/3-directives/skipto/skiptomuch.6502 | 2 ++ test/3-directives/skipto/skiptonoexpr.fail.6502 | 2 ++ test/3-directives/skipto/skiptorange1.fail.6502 | 2 ++ test/3-directives/skipto/skiptorange2.fail.6502 | 2 ++ 8 files changed, 29 insertions(+) create mode 100644 test/3-directives/skipto/skipto.6502 create mode 100644 test/3-directives/skipto/skipto.gold.ssd create mode 100644 test/3-directives/skipto/skiptoback.fail.6502 create mode 100644 test/3-directives/skipto/skiptoextra.fail.6502 create mode 100644 test/3-directives/skipto/skiptomuch.6502 create mode 100644 test/3-directives/skipto/skiptonoexpr.fail.6502 create mode 100644 test/3-directives/skipto/skiptorange1.fail.6502 create mode 100644 test/3-directives/skipto/skiptorange2.fail.6502 diff --git a/test/3-directives/skipto/skipto.6502 b/test/3-directives/skipto/skipto.6502 new file mode 100644 index 0000000..1d9b8ba --- /dev/null +++ b/test/3-directives/skipto/skipto.6502 @@ -0,0 +1,16 @@ +\ Various SKIPTOs + +ORG &2000 + +.start + +FOR s, 0, 300, 6 + EQUW s + .block + SKIPTO P%+s + ASSERT(P%=block+s) +NEXT + +.end + +SAVE "test", start, end diff --git a/test/3-directives/skipto/skipto.gold.ssd b/test/3-directives/skipto/skipto.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..d0c1d6f75c341495a39007f47e3743f48d21a254 GIT binary patch literal 8448 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgH#A;-WpvOEs*EgRI!JSbilLg7I;5?I6_LUK4| zAQHqSD=h-1AWIt{bBJP_5v2-KkqxF02_jA;sKuq!1D6EJ;(jFSz^@?$zciV$QDo{P zMq2_giYQW(Mv-pfHRlkolnN?~s9*-EMpTfhfokgOsAdv*2DOl<jmo;ZsB9i3#!a9^ zGfnhPqlu}M8#;$_BWP&CA{v@aEu&XZ%OIMYvySFAP}hPj)HRMyrtPAWJ+!dq04)rq ztC`2>Y8&k=J3~982f*Y@17Ihut-L{N3kJ;mdjn=OeJp-LA8Q8KmRAF8KRvDgKu^mC zg<anUg)8Xqh(GkVauC_b#5jl?GeAz^7!3nx$n%Yc1WFnd84VAV@PKfnMneTls6d1i cM#BbY*gz!IMni}|2tlNEM#G7ya2nc*007t-0ssI2 literal 0 HcmV?d00001 diff --git a/test/3-directives/skipto/skiptoback.fail.6502 b/test/3-directives/skipto/skiptoback.fail.6502 new file mode 100644 index 0000000..4b4e316 --- /dev/null +++ b/test/3-directives/skipto/skiptoback.fail.6502 @@ -0,0 +1,3 @@ +\ SKIPTO backwards +ORG &2000 +SKIPTO &1FFF diff --git a/test/3-directives/skipto/skiptoextra.fail.6502 b/test/3-directives/skipto/skiptoextra.fail.6502 new file mode 100644 index 0000000..dd091fd --- /dev/null +++ b/test/3-directives/skipto/skiptoextra.fail.6502 @@ -0,0 +1,2 @@ +\ Unterminated SKIPTO +SKIPTO 5, diff --git a/test/3-directives/skipto/skiptomuch.6502 b/test/3-directives/skipto/skiptomuch.6502 new file mode 100644 index 0000000..aa72f7b --- /dev/null +++ b/test/3-directives/skipto/skiptomuch.6502 @@ -0,0 +1,2 @@ +\ SKIPTO as much as possible +SKIPTO &10000 diff --git a/test/3-directives/skipto/skiptonoexpr.fail.6502 b/test/3-directives/skipto/skiptonoexpr.fail.6502 new file mode 100644 index 0000000..d694796 --- /dev/null +++ b/test/3-directives/skipto/skiptonoexpr.fail.6502 @@ -0,0 +1,2 @@ +\ SKIPTO with no expression +SKIPTO diff --git a/test/3-directives/skipto/skiptorange1.fail.6502 b/test/3-directives/skipto/skiptorange1.fail.6502 new file mode 100644 index 0000000..1521056 --- /dev/null +++ b/test/3-directives/skipto/skiptorange1.fail.6502 @@ -0,0 +1,2 @@ +\ SKIPTO before memory +SKIPTO -1 diff --git a/test/3-directives/skipto/skiptorange2.fail.6502 b/test/3-directives/skipto/skiptorange2.fail.6502 new file mode 100644 index 0000000..c70884f --- /dev/null +++ b/test/3-directives/skipto/skiptorange2.fail.6502 @@ -0,0 +1,2 @@ +\ SKIPTO after memory +SKIPTO &10001 From 11c8f4e45b5fc4a09ff7f07850792903ba8a2a3c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 21:01:48 +0100 Subject: [PATCH 105/144] CLEAR tests --- test/3-directives/clear/clear.6502 | 18 ++++++++++++++++++ test/3-directives/clear/clear.gold.ssd | Bin 0 -> 768 bytes .../clear/clearoffbyone1.fail.6502 | 18 ++++++++++++++++++ .../clear/clearoffbyone2.fail.6502 | 18 ++++++++++++++++++ .../3-directives/clear/clearsyntax1.fail.6502 | 2 ++ .../3-directives/clear/clearsyntax2.fail.6502 | 2 ++ .../3-directives/clear/clearsyntax3.fail.6502 | 2 ++ .../3-directives/clear/clearsyntax4.fail.6502 | 2 ++ 8 files changed, 62 insertions(+) create mode 100644 test/3-directives/clear/clear.6502 create mode 100644 test/3-directives/clear/clear.gold.ssd create mode 100644 test/3-directives/clear/clearoffbyone1.fail.6502 create mode 100644 test/3-directives/clear/clearoffbyone2.fail.6502 create mode 100644 test/3-directives/clear/clearsyntax1.fail.6502 create mode 100644 test/3-directives/clear/clearsyntax2.fail.6502 create mode 100644 test/3-directives/clear/clearsyntax3.fail.6502 create mode 100644 test/3-directives/clear/clearsyntax4.fail.6502 diff --git a/test/3-directives/clear/clear.6502 b/test/3-directives/clear/clear.6502 new file mode 100644 index 0000000..11bf6ec --- /dev/null +++ b/test/3-directives/clear/clear.6502 @@ -0,0 +1,18 @@ +\ CLEAR - assemble over same memory twice + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +CLEAR start, P% + +ORG &2000 +LDA #'T' +JMP &FFEE + +.end + +SAVE "test", start, end diff --git a/test/3-directives/clear/clear.gold.ssd b/test/3-directives/clear/clear.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..2daaab802c70611c2e9146a66fd3aa0cb525cf02 GIT binary patch literal 768 wcmZQzfPj+J;t~Y~1r;cFWKkT<3JgHV%D})hvOLbfurkEw-T#s1X(ayw0LD!P?EnA( literal 0 HcmV?d00001 diff --git a/test/3-directives/clear/clearoffbyone1.fail.6502 b/test/3-directives/clear/clearoffbyone1.fail.6502 new file mode 100644 index 0000000..7d56b4f --- /dev/null +++ b/test/3-directives/clear/clearoffbyone1.fail.6502 @@ -0,0 +1,18 @@ +\ CLEAR - fail to clear the first byte of a range + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +CLEAR start+1, P% + +ORG &2000 +LDA #'T' +JMP &FFEE + +.end + +SAVE "test", start, end diff --git a/test/3-directives/clear/clearoffbyone2.fail.6502 b/test/3-directives/clear/clearoffbyone2.fail.6502 new file mode 100644 index 0000000..286441c --- /dev/null +++ b/test/3-directives/clear/clearoffbyone2.fail.6502 @@ -0,0 +1,18 @@ +\ CLEAR - fail to clear the last byte of a range + +ORG &2000 + +.start + +LDA #'C' +JMP &FFEE + +CLEAR start, P%-1 + +ORG &2000 +LDA #'T' +JMP &FFEE + +.end + +SAVE "test", start, end diff --git a/test/3-directives/clear/clearsyntax1.fail.6502 b/test/3-directives/clear/clearsyntax1.fail.6502 new file mode 100644 index 0000000..b0b23d9 --- /dev/null +++ b/test/3-directives/clear/clearsyntax1.fail.6502 @@ -0,0 +1,2 @@ +\ CLEAR with no parameters +CLEAR diff --git a/test/3-directives/clear/clearsyntax2.fail.6502 b/test/3-directives/clear/clearsyntax2.fail.6502 new file mode 100644 index 0000000..39d3e88 --- /dev/null +++ b/test/3-directives/clear/clearsyntax2.fail.6502 @@ -0,0 +1,2 @@ +\ CLEAR with one parameter +CLEAR &2000 diff --git a/test/3-directives/clear/clearsyntax3.fail.6502 b/test/3-directives/clear/clearsyntax3.fail.6502 new file mode 100644 index 0000000..a40e43f --- /dev/null +++ b/test/3-directives/clear/clearsyntax3.fail.6502 @@ -0,0 +1,2 @@ +\ CLEAR with one parameter and a comma +CLEAR &2000 , diff --git a/test/3-directives/clear/clearsyntax4.fail.6502 b/test/3-directives/clear/clearsyntax4.fail.6502 new file mode 100644 index 0000000..6e01fea --- /dev/null +++ b/test/3-directives/clear/clearsyntax4.fail.6502 @@ -0,0 +1,2 @@ +\ CLEAR with two parameters and a comma +CLEAR &2000 , &2000 , From c89388e14ea0ea462f695bfaca313d10baf7e487 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 21:03:58 +0100 Subject: [PATCH 106/144] Typo in unexpected comma message --- src/asmexception.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/asmexception.h b/src/asmexception.h index 372d1d4..243218a 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -207,7 +207,7 @@ DEFINE_SYNTAX_EXCEPTION( ParameterCount, "Wrong number of parameters." ); DEFINE_SYNTAX_EXCEPTION( NoImplied, "Implied mode not allowed for this instruction." ); DEFINE_SYNTAX_EXCEPTION( ImmTooLarge, "Immediate constants cannot be greater than 255." ); DEFINE_SYNTAX_EXCEPTION( ImmNegative, "Constant cannot be negative." ); -DEFINE_SYNTAX_EXCEPTION( UnexpectedComma, "Unexpected comma enountered." ); +DEFINE_SYNTAX_EXCEPTION( UnexpectedComma, "Unexpected comma encountered." ); DEFINE_SYNTAX_EXCEPTION( NoImmediate, "Immediate mode not allowed for this instruction." ); DEFINE_SYNTAX_EXCEPTION( NoIndirect, "Indirect mode not allowed for this instruction." ); DEFINE_SYNTAX_EXCEPTION( 6502Bug, "JMP (addr) will not execute as intended due to the 6502 bug (addr = &xxFF)." ); From 620e84fef827f4a7b8316024b604befc65fc9f64 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 14 Jul 2021 21:13:28 +0100 Subject: [PATCH 107/144] Update test/README.md with .gold.txt feature --- test/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/README.md b/test/README.md index ee0cc02..cb92e07 100644 --- a/test/README.md +++ b/test/README.md @@ -33,3 +33,7 @@ For example, if a directory contains `sometest.6502` and `sometest.gold.ssd` the the test will be required to produce a `test.ssd` file that is identical to `sometest.gold.ssd`. Note that the output file is always called `test.ssd`. +Similarly, if a test file has a corresponding `.gold.txt` file this is assumed to +be part of the stdout/stderr output from running the test. The test runner will +capture the output and check it contains the text from the `.gold.txt` file. + From adaafd76cc0687b0aefd4f1417cf214acea30fb6 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 15 Jul 2021 22:00:59 +0100 Subject: [PATCH 108/144] The symbol VERBOSE controls verbose output If -v (verbose) or -q (quiet) is specified on the command-line then the VERBOSE symbol is ignored. Otherwise verbose output is controlled with VERBOSE=0 and VERBOSE=1. Plus a minor change to the testrunner to put the -v switch before the test-specific switches, so it can be overridden with -q. --- README.md | 7 ++- src/assemble.cpp | 7 +-- src/commands.cpp | 20 ++++---- src/expression.cpp | 16 +----- src/globaldata.cpp | 1 + src/globaldata.h | 6 ++- src/lineparser.cpp | 4 +- src/main.cpp | 4 ++ src/sourcecode.cpp | 54 ++++++++++++++++++++ src/sourcecode.h | 4 ++ src/sourcefile.cpp | 2 +- test/5-errors/filelinecallstackdemo.6502 | 2 + test/5-errors/filelinecallstackdemo.gold.txt | 26 ++-------- test/testrunner.py | 4 +- 14 files changed, 99 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 3c7fb90..cf82e44 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,12 @@ If specified, BeebAsm will use this disc image as a template for the new disc im `-v` -Verbose output. Assembled code will be output to the screen. +Force verbose output. Assembled code will be output to the screen. The VERBOSE symbol will be ignored. + +`-q` + +Force quiet (non-verbose) output. The VERBOSE symbol will be ignored. If neither `-v` or `-q` is used then verbose +output can be controlled by setting `VERBOSE=0` or `VERBOSE=1`. The value can be changed only in a new scope. `-vc` diff --git a/src/assemble.cpp b/src/assemble.cpp index 79347a0..e4846e1 100644 --- a/src/assemble.cpp +++ b/src/assemble.cpp @@ -31,6 +31,7 @@ #include "globaldata.h" #include "objectcode.h" #include "asmexception.h" +#include "sourcecode.h" using namespace std; @@ -233,7 +234,7 @@ void LineParser::Assemble1( int instructionIndex, ADDRESSING_MODE mode ) { assert( HasAddressingMode( instructionIndex, mode ) ); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -272,7 +273,7 @@ void LineParser::Assemble2( int instructionIndex, ADDRESSING_MODE mode, unsigned assert( value < 0x100 ); assert( HasAddressingMode( instructionIndex, mode ) ); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -346,7 +347,7 @@ void LineParser::Assemble3( int instructionIndex, ADDRESSING_MODE mode, unsigned assert( value < 0x10000 ); assert( HasAddressingMode( instructionIndex, mode ) ); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; diff --git a/src/commands.cpp b/src/commands.cpp index 464b31a..66d7dfa 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -220,7 +220,7 @@ void LineParser::HandleDefineLabel() SymbolTable::Instance().AddLabel(symbolName); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "." << symbolName << endl; } @@ -517,7 +517,7 @@ void LineParser::HandleSkip() throw AsmException_SyntaxError_ImmNegative( m_line, oldColumn ); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << endl; @@ -604,7 +604,7 @@ void LineParser::HandleInclude() string filename = EvaluateExpressionAsString(); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cerr << "Including file " << filename << endl; } @@ -695,7 +695,7 @@ void LineParser::HandleEqub() throw AsmException_SyntaxError_NumberTooBig( m_line, m_column ); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -744,7 +744,7 @@ void LineParser::HandleEqub() /*************************************************************************************************/ void LineParser::HandleEqus( const String& equs ) { - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -754,7 +754,7 @@ void LineParser::HandleEqus( const String& equs ) { int mappedchar = ObjectCode::Instance().GetMapping( equs[ i ] ); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { if ( i < 3 ) { @@ -779,7 +779,7 @@ void LineParser::HandleEqus( const String& equs ) } } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << endl << nouppercase << dec << setfill( ' ' ); } @@ -819,7 +819,7 @@ void LineParser::HandleEquw() throw AsmException_SyntaxError_NumberTooBig( m_line, m_column ); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -889,7 +889,7 @@ void LineParser::HandleEqud() } } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -1136,7 +1136,7 @@ void LineParser::HandleSave() } } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "Saving file '" << saveFile << "'" << endl; } diff --git a/src/expression.cpp b/src/expression.cpp index 34ea321..51e585b 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -278,21 +278,7 @@ Value LineParser::GetValue() { // Regular symbol - bool bFoundSymbol = false; - - for ( int forLevel = m_sourceCode->GetForLevel(); forLevel >= 0; forLevel-- ) - { - string fullSymbolName = symbolName + m_sourceCode->GetSymbolNameSuffix( forLevel ); - - if ( SymbolTable::Instance().IsSymbolDefined( fullSymbolName ) ) - { - value = SymbolTable::Instance().GetSymbol( fullSymbolName ); - bFoundSymbol = true; - break; - } - } - - if ( !bFoundSymbol ) + if ( !m_sourceCode->GetSymbolValue(symbolName, value) ) { // symbol not known throw AsmException_SyntaxError_SymbolNotDefined( m_line, oldColumn ); diff --git a/src/globaldata.cpp b/src/globaldata.cpp index 9fc0457..afe3040 100644 --- a/src/globaldata.cpp +++ b/src/globaldata.cpp @@ -69,6 +69,7 @@ void GlobalData::Destroy() /*************************************************************************************************/ GlobalData::GlobalData() : m_pBootFile( NULL ), + m_bVerboseSet( false ), m_bVerbose( false ), m_bUseDiscImage( false ), m_pDiscImage( NULL ), diff --git a/src/globaldata.h b/src/globaldata.h index 4e6afbf..5678119 100644 --- a/src/globaldata.h +++ b/src/globaldata.h @@ -41,7 +41,7 @@ class GlobalData inline void SetPass( int i ) { m_pass = i; } inline void SetBootFile( const char* p ) { m_pBootFile = p; } - inline void SetVerbose( bool b ) { m_bVerbose = b; } + inline void SetVerbose( bool b ) { m_bVerboseSet = true; m_bVerbose = b; } inline void SetUseDiscImage( bool b ) { m_bUseDiscImage = b; } inline void SetDiscImage( DiscImage* d ) { m_pDiscImage = d; } inline void ResetForId() { m_forId = 0; } @@ -60,7 +60,8 @@ class GlobalData inline int GetPass() const { return m_pass; } inline bool IsFirstPass() const { return ( m_pass == 0 ); } inline bool IsSecondPass() const { return ( m_pass == 1 ); } - inline bool ShouldOutputAsm() const { return ( m_pass == 1 && m_bVerbose ); } + inline bool IsVerboseSet() const { return m_bVerboseSet; } + inline bool IsVerbose() const { return m_bVerbose; } inline const char* GetBootFile() const { return m_pBootFile; } inline bool UsesDiscImage() const { return m_bUseDiscImage; } inline DiscImage* GetDiscImage() const { return m_pDiscImage; } @@ -85,6 +86,7 @@ class GlobalData int m_pass; const char* m_pBootFile; + bool m_bVerboseSet; bool m_bVerbose; bool m_bUseDiscImage; DiscImage* m_pDiscImage; diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 1dcec2b..ce8028a 100644 --- a/src/lineparser.cpp +++ b/src/lineparser.cpp @@ -214,7 +214,7 @@ void LineParser::Process() const Macro* macro = MacroTable::Instance().Get( macroName ); if ( macro != NULL ) { - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "Macro " << macroName << ":" << endl; } @@ -273,7 +273,7 @@ void LineParser::Process() HandleCloseBrace(); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "End macro " << macroName << endl; } diff --git a/src/main.cpp b/src/main.cpp index 91ea4e8..8e714be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -143,6 +143,10 @@ int main( int argc, char* argv[] ) { GlobalData::Instance().SetVerbose( true ); } + else if ( strcmp( argv[i], "-q" ) == 0 ) + { + GlobalData::Instance().SetVerbose( false ); + } else if ( strcmp( argv[i], "-d" ) == 0 ) { bDumpSymbols = true; diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 654fce1..aef44e0 100644 --- a/src/sourcecode.cpp +++ b/src/sourcecode.cpp @@ -571,3 +571,57 @@ bool SourceCode::IsRealForLevel( int level ) const assert( level <= m_forStackPtr ); return m_forStack[ level - 1 ].m_step != 0.0; } + + + +/*************************************************************************************************/ +/** + SourceCode::GetSymbolValue() + + Search up the stack for a value for a symbol. N.B. This is dynamic scoping. +*/ +/*************************************************************************************************/ +bool SourceCode::GetSymbolValue(std::string name, Value& value) +{ + for ( int forLevel = GetForLevel(); forLevel >= 0; forLevel-- ) + { + string fullSymbolName = name + GetSymbolNameSuffix( forLevel ); + + if ( SymbolTable::Instance().IsSymbolDefined( fullSymbolName ) ) + { + value = SymbolTable::Instance().GetSymbol( fullSymbolName ); + return true; + } + } + return false; +} + + + +/*************************************************************************************************/ +/** + SourceCode::ShouldOutputAsm() + + Return true if verbose output is required. +*/ +/*************************************************************************************************/ +bool SourceCode::ShouldOutputAsm() +{ + if (!GlobalData::Instance().IsSecondPass()) + return false; + + if (GlobalData::Instance().IsVerboseSet()) + { + return GlobalData::Instance().IsVerbose(); + } + + Value value; + if ( GetSymbolValue("VERBOSE", value) ) + { + if (value.GetType() != Value::NumberValue) + return false; + return value.GetNumber() != 0; + } + + return false; +} diff --git a/src/sourcecode.h b/src/sourcecode.h index 9f7f4a6..2ef01ae 100644 --- a/src/sourcecode.h +++ b/src/sourcecode.h @@ -27,6 +27,8 @@ #include <string> #include <vector> +#include "value.h" + class Macro; class SourceCode @@ -120,7 +122,9 @@ class SourceCode inline int GetInitialForStackPtr() const { return m_initialForStackPtr; } inline Macro* GetCurrentMacro() { return m_currentMacro; } + bool GetSymbolValue(std::string name, Value& value); std::string GetSymbolNameSuffix( int level = -1 ) const; + bool ShouldOutputAsm(); bool IsIfConditionTrue() const; void AddIfLevel( const std::string& line, int column ); diff --git a/src/sourcefile.cpp b/src/sourcefile.cpp index 7db14e3..401007a 100644 --- a/src/sourcefile.cpp +++ b/src/sourcefile.cpp @@ -94,7 +94,7 @@ void SourceFile::Process() // Display ok message - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( ShouldOutputAsm() ) { cerr << "Processed file '" << m_filename << "' ok" << endl; } diff --git a/test/5-errors/filelinecallstackdemo.6502 b/test/5-errors/filelinecallstackdemo.6502 index fe441e8..c89c543 100644 --- a/test/5-errors/filelinecallstackdemo.6502 +++ b/test/5-errors/filelinecallstackdemo.6502 @@ -1,3 +1,5 @@ +\ beebasm -q + org &2000 macro foo n diff --git a/test/5-errors/filelinecallstackdemo.gold.txt b/test/5-errors/filelinecallstackdemo.gold.txt index 62df925..212402c 100644 --- a/test/5-errors/filelinecallstackdemo.gold.txt +++ b/test/5-errors/filelinecallstackdemo.gold.txt @@ -1,22 +1,4 @@ -.start - 2000 A0 2A LDY #&2A -Current location:filelinecallstackdemo.6502:14 - 2002 A2 00 LDX #&00 -.loop -Macro foo: -Macro bar: - 2004 A9 18 LDA #&18 -Current call stack:filelinecallstackdemo.6502:9 -filelinecallstackdemo.6502:4 -filelinecallstackdemo.6502:17 (end) -End macro bar -End macro foo - 2006 9D 00 30 STA &3000,X - 2009 C8 INY - 200A E8 INX - 200B E0 04 CPX #&04 - 200D D0 F5 BNE &2004 - 200F 60 RTS -.end -Saving file 'test' -Processed file 'filelinecallstackdemo.6502' ok +Current location:filelinecallstackdemo.6502:16 +Current call stack:filelinecallstackdemo.6502:11 +filelinecallstackdemo.6502:6 +filelinecallstackdemo.6502:19 (end) diff --git a/test/testrunner.py b/test/testrunner.py index 7ede6f3..8ec3ef9 100644 --- a/test/testrunner.py +++ b/test/testrunner.py @@ -75,10 +75,10 @@ def read_beebasm_switches(file_name): return params[1:] def beebasm_args(beebasm, file_name, ssd_name): - args = [beebasm] + read_beebasm_switches(file_name) + args = [beebasm, '-v'] + read_beebasm_switches(file_name) if ssd_name != None: args += ['-do', ssd_name] - args += ['-v', '-i', file_name] + args += ['-i', file_name] return args def execute(args, output): From 6ea779e05da27a330577c4c09dd5ec7cb9b0893b Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 14:05:05 +0100 Subject: [PATCH 109/144] INCBIN tests --- test/3-directives/incbin/incbin.6502 | 15 ++++ test/3-directives/incbin/incbin.bin | 72 ++++++++++++++++++ test/3-directives/incbin/incbin.gold.ssd | Bin 0 -> 5120 bytes .../incbin/incbinjustfails.fail.6502 | 14 ++++ test/3-directives/incbin/incbinjustfits.6502 | 14 ++++ .../incbin/incbinjustfits.gold.ssd | Bin 0 -> 5120 bytes .../incbin/incbinnoname.fail.6502 | 15 ++++ 7 files changed, 130 insertions(+) create mode 100644 test/3-directives/incbin/incbin.6502 create mode 100644 test/3-directives/incbin/incbin.bin create mode 100644 test/3-directives/incbin/incbin.gold.ssd create mode 100644 test/3-directives/incbin/incbinjustfails.fail.6502 create mode 100644 test/3-directives/incbin/incbinjustfits.6502 create mode 100644 test/3-directives/incbin/incbinjustfits.gold.ssd create mode 100644 test/3-directives/incbin/incbinnoname.fail.6502 diff --git a/test/3-directives/incbin/incbin.6502 b/test/3-directives/incbin/incbin.6502 new file mode 100644 index 0000000..01727b6 --- /dev/null +++ b/test/3-directives/incbin/incbin.6502 @@ -0,0 +1,15 @@ +\ INCBIN - simple inclusion + +ORG &2000 + +.start + +EQUS "XX" +INCBIN "incbin.bin" +EQUS "XX" + +.end + +ASSERT(end-start=4561) + +SAVE "test", start, end diff --git a/test/3-directives/incbin/incbin.bin b/test/3-directives/incbin/incbin.bin new file mode 100644 index 0000000..def6cae --- /dev/null +++ b/test/3-directives/incbin/incbin.bin @@ -0,0 +1,72 @@ +There were four of us - George, and William Samuel Harris, and myself, and +Montmorency. We were sitting in my room, smoking, and talking about how +bad we were - bad from a medical point of view I mean, of course. + +We were all feeling seedy, and we were getting quite nervous about it. +Harris said he felt such extraordinary fits of giddiness come over him at +times, that he hardly knew what he was doing; and then George said that +_he_ had fits of giddiness too, and hardly knew what _he_ was doing. +With me, it was my liver that was out of order. I knew it was my liver +that was out of order, because I had just been reading a patent +liver-pill circular, in which were detailed the various symptoms by which +a man could tell when his liver was out of order. I had them all. + +It is a most extraordinary thing, but I never read a patent medicine +advertisement without being impelled to the conclusion that I am +suffering from the particular disease therein dealt with in its most +virulent form. The diagnosis seems in every case to correspond exactly +with all the sensations that I have ever felt. + +[Picture: Man reading book] I remember going to the British Museum one +day to read up the treatment for some slight ailment of which I had a +touch - hay fever, I fancy it was. I got down the book, and read all I +came to read; and then, in an unthinking moment, I idly turned the +leaves, and began to indolently study diseases, generally. I forget +which was the first distemper I plunged into - some fearful, devastating +scourge, I know - and, before I had glanced half down the list of +"premonitory symptoms," it was borne in upon me that I had fairly got it. + +I sat for awhile, frozen with horror; and then, in the listlessness of +despair, I again turned over the pages. I came to typhoid fever - read +the symptoms - discovered that I had typhoid fever, must have had it for +months without knowing it - wondered what else I had got; turned up St. +Vitus's Dance - found, as I expected, that I had that too, - began to get +interested in my case, and determined to sift it to the bottom, and so +started alphabetically - read up ague, and learnt that I was sickening for +it, and that the acute stage would commence in about another fortnight. +Bright's disease, I was relieved to find, I had only in a modified form, +and, so far as that was concerned, I might live for years. Cholera I +had, with severe complications; and diphtheria I seemed to have been born +with. I plodded conscientiously through the twenty-six letters, and the +only malady I could conclude I had not got was housemaid's knee. + +I felt rather hurt about this at first; it seemed somehow to be a sort of +slight. Why hadn't I got housemaid's knee? Why this invidious +reservation? After a while, however, less grasping feelings prevailed. +I reflected that I had every other known malady in the pharmacology, and +I grew less selfish, and determined to do without housemaid's knee. +Gout, in its most malignant stage, it would appear, had seized me without +my being aware of it; and zymosis I had evidently been suffering with +from boyhood. There were no more diseases after zymosis, so I concluded +there was nothing else the matter with me. + +I sat and pondered. I thought what an interesting case I must be from a +medical point of view, what an acquisition I should be to a class! +Students would have no need to "walk the hospitals," if they had me. I +was a hospital in myself. All they need do would be to walk round me, +and, after that, take their diploma. + +Then I wondered how long I had to live. I tried to examine myself. I +felt my pulse. I could not at first feel any pulse at all. Then, all of +a sudden, it seemed to start off. I pulled out my watch and timed it. I +made it a hundred and forty-seven to the minute. I tried to feel my +heart. I could not feel my heart. It had stopped beating. I have since +been induced to come to the opinion that it must have been there all the +time, and must have been beating, but I cannot account for it. I patted +myself all over my front, from what I call my waist up to my head, and I +went a bit round each side, and a little way up the back. But I could +not feel or hear anything. I tried to look at my tongue. I stuck it out +as far as ever it would go, and I shut one eye, and tried to examine it +with the other. I could only see the tip, and the only thing that I +could gain from that was to feel more certain than before that I had +scarlet fever. diff --git a/test/3-directives/incbin/incbin.gold.ssd b/test/3-directives/incbin/incbin.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..ff2aaaf698572b01e1fcc7a364de49e208c8e3d8 GIT binary patch literal 5120 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 Fe*r(wDhvPs literal 0 HcmV?d00001 diff --git a/test/3-directives/incbin/incbinjustfails.fail.6502 b/test/3-directives/incbin/incbinjustfails.fail.6502 new file mode 100644 index 0000000..d65f1d7 --- /dev/null +++ b/test/3-directives/incbin/incbinjustfails.fail.6502 @@ -0,0 +1,14 @@ +\ INCBIN - one byte too long for remaining memory + +ORG &EE32 + +.start + +EQUS "XX" +INCBIN "incbin.bin" + +.end + +ASSERT(P%=&10001) + +SAVE "test", start, end diff --git a/test/3-directives/incbin/incbinjustfits.6502 b/test/3-directives/incbin/incbinjustfits.6502 new file mode 100644 index 0000000..0cae6d4 --- /dev/null +++ b/test/3-directives/incbin/incbinjustfits.6502 @@ -0,0 +1,14 @@ +\ INCBIN - fit exactly in remaining memory + +ORG &EE31 + +.start + +EQUS "XX" +INCBIN "incbin.bin" + +.end + +ASSERT(P%=&10000) + +SAVE "test", start, end diff --git a/test/3-directives/incbin/incbinjustfits.gold.ssd b/test/3-directives/incbin/incbinjustfits.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..e868eced5f8f0c9c8cb9e69d0fe0390eff5f3103 GIT binary patch literal 5120 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 I`_ETD0l_dUSO5S3 literal 0 HcmV?d00001 diff --git a/test/3-directives/incbin/incbinnoname.fail.6502 b/test/3-directives/incbin/incbinnoname.fail.6502 new file mode 100644 index 0000000..42160e4 --- /dev/null +++ b/test/3-directives/incbin/incbinnoname.fail.6502 @@ -0,0 +1,15 @@ +\ INCBIN - missing file name + +ORG &2000 + +.start + +EQUS "XX" +INCBIN +EQUS "XX" + +.end + +ASSERT(end-start=4561) + +SAVE "test", start, end From 04269a34b623712830a54781da44f4d3757e35fa Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 14:49:03 +0100 Subject: [PATCH 110/144] MAPCHAR tests --- test/3-directives/mapchar/mapchar.6502 | 19 ++++++++++++++++++ test/3-directives/mapchar/mapchar.gold.ssd | Bin 0 -> 768 bytes .../mapchar/mapcharfour.fail.6502 | 6 ++++++ .../3-directives/mapchar/mapcharone.fail.6502 | 6 ++++++ 4 files changed, 31 insertions(+) create mode 100644 test/3-directives/mapchar/mapchar.6502 create mode 100644 test/3-directives/mapchar/mapchar.gold.ssd create mode 100644 test/3-directives/mapchar/mapcharfour.fail.6502 create mode 100644 test/3-directives/mapchar/mapcharone.fail.6502 diff --git a/test/3-directives/mapchar/mapchar.6502 b/test/3-directives/mapchar/mapchar.6502 new file mode 100644 index 0000000..6f970be --- /dev/null +++ b/test/3-directives/mapchar/mapchar.6502 @@ -0,0 +1,19 @@ +\ MAPCHAR - some mapping + +ORG &2000 + +MAPCHAR 'A','Y','B' +MAPCHAR 'Z','A' +MAPCHAR 'a','y','b' +MAPCHAR 'z','a' +MAPCHAR '>',' ' +MAPCHAR '<',',' + +.start + +EQUS "ROGHMW>NE>AKZBJ>PTZQSY<>ITCFD>LX>UNV", 10 +EQUS "sgd>pthbj>aqnvm>enw>itlor>nudq>sgd>kzyx>cnf", 10 + +.end + +SAVE "test", start, end diff --git a/test/3-directives/mapchar/mapchar.gold.ssd b/test/3-directives/mapchar/mapchar.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..e36c63328000f18d3632d76787e1669fb467b178 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFP$iTofvOLbf5FFs)=@+5k@2247<LK<I5E$wh z6cVMQ;1%lP?yBG$sSxHL&Q+3;s!&*(nVhYVRFq$yr;wIkp^#OYTTrZ!UzS>=0Fus0 Ptg2K<$xr7RVgU#MpaLAg literal 0 HcmV?d00001 diff --git a/test/3-directives/mapchar/mapcharfour.fail.6502 b/test/3-directives/mapchar/mapcharfour.fail.6502 new file mode 100644 index 0000000..e7a3784 --- /dev/null +++ b/test/3-directives/mapchar/mapcharfour.fail.6502 @@ -0,0 +1,6 @@ +\ MAPCHAR - two many parameters + +ORG &2000 + +MAPCHAR 'A','B','C','D' + diff --git a/test/3-directives/mapchar/mapcharone.fail.6502 b/test/3-directives/mapchar/mapcharone.fail.6502 new file mode 100644 index 0000000..080bfda --- /dev/null +++ b/test/3-directives/mapchar/mapcharone.fail.6502 @@ -0,0 +1,6 @@ +\ MAPCHAR - insufficient parameters + +ORG &2000 + +MAPCHAR 'A' + From bef2416eb848425a8522e09b51160a0c43e9fb96 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 16:07:50 +0100 Subject: [PATCH 111/144] Better error reporting for COPYBLOCK assembly errors --- src/commands.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commands.cpp b/src/commands.cpp index 464b31a..ebd742c 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1912,7 +1912,16 @@ void LineParser::HandleCopyBlock() if ( GlobalData::Instance().IsSecondPass() ) { - ObjectCode::Instance().CopyBlock( start, end, dest ); + try + { + ObjectCode::Instance().CopyBlock( start, end, dest ); + } + catch ( AsmException_AssembleError& e ) + { + e.SetString( m_line ); + e.SetColumn( m_column ); + throw; + } } if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) From 54bbcd232bb82b77a6758ecf3b02b4338a7f5c2c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 16:08:47 +0100 Subject: [PATCH 112/144] Execute COPYBLOCK on both passes This is necessary to set the memory guard flags. --- src/commands.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index ebd742c..b51ef50 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1910,18 +1910,15 @@ void LineParser::HandleCopyBlock() throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); } - if ( GlobalData::Instance().IsSecondPass() ) + try { - try - { - ObjectCode::Instance().CopyBlock( start, end, dest ); - } - catch ( AsmException_AssembleError& e ) - { - e.SetString( m_line ); - e.SetColumn( m_column ); - throw; - } + ObjectCode::Instance().CopyBlock( start, end, dest ); + } + catch ( AsmException_AssembleError& e ) + { + e.SetString( m_line ); + e.SetColumn( m_column ); + throw; } if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) From ef47d95fc2af7df5bc15cfed503f4afd643394ba Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 16:18:28 +0100 Subject: [PATCH 113/144] COPYBLOCK tests --- test/3-directives/copyblock/copyblock.6502 | 20 ++++++++++++ .../3-directives/copyblock/copyblock.gold.ssd | Bin 0 -> 768 bytes .../copyblock/copyblockjustfails.fail.6502 | 22 +++++++++++++ .../copyblock/copyblockjustfits.6502 | 22 +++++++++++++ .../copyblock/copyblockjustfits.gold.ssd | Bin 0 -> 768 bytes .../copyblock/copyblockreuse.6502 | 29 ++++++++++++++++++ .../copyblock/copyblockreuse.gold.ssd | Bin 0 -> 768 bytes .../copyblock/copyblockreusedest.fail.6502 | 22 +++++++++++++ 8 files changed, 115 insertions(+) create mode 100644 test/3-directives/copyblock/copyblock.6502 create mode 100644 test/3-directives/copyblock/copyblock.gold.ssd create mode 100644 test/3-directives/copyblock/copyblockjustfails.fail.6502 create mode 100644 test/3-directives/copyblock/copyblockjustfits.6502 create mode 100644 test/3-directives/copyblock/copyblockjustfits.gold.ssd create mode 100644 test/3-directives/copyblock/copyblockreuse.6502 create mode 100644 test/3-directives/copyblock/copyblockreuse.gold.ssd create mode 100644 test/3-directives/copyblock/copyblockreusedest.fail.6502 diff --git a/test/3-directives/copyblock/copyblock.6502 b/test/3-directives/copyblock/copyblock.6502 new file mode 100644 index 0000000..9c45d54 --- /dev/null +++ b/test/3-directives/copyblock/copyblock.6502 @@ -0,0 +1,20 @@ +\ COPYBLOCK - simple copy + +ORG &2000 + +.start + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +SKIP loopend-start +COPYBLOCK start,loopend,loopend + +.end + +SAVE "test", start, end diff --git a/test/3-directives/copyblock/copyblock.gold.ssd b/test/3-directives/copyblock/copyblock.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..6d6152c5d1560b0462065e20c1966b22df78e9f0 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFf#K6EbvOLbfuz+D>!Nj7D3qRqEkre^}eqRjk literal 0 HcmV?d00001 diff --git a/test/3-directives/copyblock/copyblockjustfails.fail.6502 b/test/3-directives/copyblock/copyblockjustfails.fail.6502 new file mode 100644 index 0000000..be0c05d --- /dev/null +++ b/test/3-directives/copyblock/copyblockjustfails.fail.6502 @@ -0,0 +1,22 @@ +\ COPYBLOCK - copy one byte too much + +ORG &FFEF + +.start + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +SKIP loopend-start-1 +COPYBLOCK start,loopend,loopend + +.end + +ASSERT(end=&10000) + +SAVE "test", start, end diff --git a/test/3-directives/copyblock/copyblockjustfits.6502 b/test/3-directives/copyblock/copyblockjustfits.6502 new file mode 100644 index 0000000..5496db2 --- /dev/null +++ b/test/3-directives/copyblock/copyblockjustfits.6502 @@ -0,0 +1,22 @@ +\ COPYBLOCK - copy fits exactly into remaining memory + +ORG &FFEE + +.start + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +SKIP loopend-start +COPYBLOCK start,loopend,loopend + +.end + +ASSERT(end=&10000) + +SAVE "test", start, end diff --git a/test/3-directives/copyblock/copyblockjustfits.gold.ssd b/test/3-directives/copyblock/copyblockjustfits.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..4e45037ed965d5c3c0037be4151810090b04944a GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3h(~E`!B@6z%;Tv&cLvMVPnC>qK*qc;f#?L0sxSp B5QqQ( literal 0 HcmV?d00001 diff --git a/test/3-directives/copyblock/copyblockreuse.6502 b/test/3-directives/copyblock/copyblockreuse.6502 new file mode 100644 index 0000000..ad4af51 --- /dev/null +++ b/test/3-directives/copyblock/copyblockreuse.6502 @@ -0,0 +1,29 @@ +\ COPYBLOCK - reuse memory from source of copy + +ORG &2000 + +.start + +\ This CLEAR is required otherwise the second pass grumbles here +\ about the LDX #&00 later in the first pass. This is rather +\ unintuitive. +CLEAR start, start+2 + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +SKIP loopend-start +COPYBLOCK start,loopend,loopend + +.end + +ORG start +\ This would fail if the block hadn't been copied +LDX #&00 + +SAVE "test", start, end diff --git a/test/3-directives/copyblock/copyblockreuse.gold.ssd b/test/3-directives/copyblock/copyblockreuse.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..61525ccb884363892e310ed3ae50cfde09b4b897 GIT binary patch literal 768 zcmZQzfPj+J;t~Y~1r;cFWKkT<3JgFf#K6EbvOLbfu!vz}!Nj7D3qKdY7$YkL0DxZ% A@Bjb+ literal 0 HcmV?d00001 diff --git a/test/3-directives/copyblock/copyblockreusedest.fail.6502 b/test/3-directives/copyblock/copyblockreusedest.fail.6502 new file mode 100644 index 0000000..8cab72a --- /dev/null +++ b/test/3-directives/copyblock/copyblockreusedest.fail.6502 @@ -0,0 +1,22 @@ +\ COPYBLOCK - try to reuse copy destination + +ORG &2000 + +.start + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +COPYBLOCK start,loopend,loopend + +\ This should fail because it overlaps the copy +LDA #&00 + +.end + +SAVE "test", start, end From dad0a3ebab78b3057bdcec945ddae92a31315000 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 21:24:56 +0100 Subject: [PATCH 114/144] PUTFILE and PUTTEXT tests --- test/3-directives/putfiletext/put.txt | 71 ++++++++++++++++++ test/3-directives/putfiletext/putfile.6502 | 13 ++++ .../3-directives/putfiletext/putfile.gold.ssd | Bin 0 -> 8960 bytes .../putfilenonexistentdemo.fail.6502 | 0 .../putfiletext/putsyntax1.fail.6502 | 5 ++ .../putfiletext/putsyntax2.fail.6502 | 5 ++ .../putfiletext/putsyntax3.fail.6502 | 5 ++ .../putfiletext/putsyntax4.fail.6502 | 5 ++ .../putfiletext/putsyntax5.fail.6502 | 5 ++ test/3-directives/putfiletext/puttext.6502 | 10 +++ .../3-directives/putfiletext/puttext.gold.ssd | Bin 0 -> 3328 bytes 11 files changed, 119 insertions(+) create mode 100644 test/3-directives/putfiletext/put.txt create mode 100644 test/3-directives/putfiletext/putfile.6502 create mode 100644 test/3-directives/putfiletext/putfile.gold.ssd rename test/3-directives/{ => putfiletext}/putfilenonexistentdemo.fail.6502 (100%) create mode 100644 test/3-directives/putfiletext/putsyntax1.fail.6502 create mode 100644 test/3-directives/putfiletext/putsyntax2.fail.6502 create mode 100644 test/3-directives/putfiletext/putsyntax3.fail.6502 create mode 100644 test/3-directives/putfiletext/putsyntax4.fail.6502 create mode 100644 test/3-directives/putfiletext/putsyntax5.fail.6502 create mode 100644 test/3-directives/putfiletext/puttext.6502 create mode 100644 test/3-directives/putfiletext/puttext.gold.ssd diff --git a/test/3-directives/putfiletext/put.txt b/test/3-directives/putfiletext/put.txt new file mode 100644 index 0000000..69635c6 --- /dev/null +++ b/test/3-directives/putfiletext/put.txt @@ -0,0 +1,71 @@ +I had walked into that reading-room a +happy, healthy man. I crawled out a decrepit wreck. + +I went to my medical man. He is an old chum of mine, and feels my pulse, +and looks at my tongue, and talks about the weather, all for nothing, +when I fancy I'm ill; so I thought I would do him a good turn by going to +him now. "What a doctor wants," I said, "is practice. He shall have me. +He will get more practice out of me than out of seventeen hundred of your +ordinary, commonplace patients, with only one or two diseases each." So +I went straight up and saw him, and he said: + +"Well, what's the matter with you?" + +I said: + +"I will not take up your time, dear boy, with telling you what is the +matter with me. Life is brief, and you might pass away before I had +finished. But I will tell you what is _not_ the matter with me. I have +not got housemaid's knee. Why I have not got housemaid's knee, I cannot +tell you; but the fact remains that I have not got it. Everything else, +however, I _have_ got." + +And I told him how I came to discover it all. + +Then he opened me and looked down me, and clutched hold of my wrist, and +then he hit me over the chest when I wasn't expecting it - a cowardly +thing to do, I call it - and immediately afterwards butted me with the +side of his head. After that, he sat down and wrote out a prescription, +and folded it up and gave it me, and I put it in my pocket and went out. + +I did not open it. I took it to the nearest chemist's, and handed it in. +The man read it, and then handed it back. + +He said he didn't keep it. + +I said: + +"You are a chemist?" + +He said: + +"I am a chemist. If I was a co-operative stores and family hotel +combined, I might be able to oblige you. Being only a chemist hampers +me." + +I read the prescription. It ran: + + "1 lb. beefsteak, with + 1 pt. bitter beer + every 6 hours. + + 1 ten-mile walk every morning. + + 1 bed at 11 sharp every night. + + And don't stuff up your head with things you don't understand." + + +I followed the directions, with the happy result - speaking for +myself - that my life was preserved, and is still going on. + +In the present instance, going back to the liver-pill circular, I had the +symptoms, beyond all mistake, the chief among them being "a general +disinclination to work of any kind." + +What I suffer in that way no tongue can tell. From my earliest infancy I +have been a martyr to it. As a boy, the disease hardly ever left me for +a day. They did not know, then, that it was my liver. Medical science +was in a far less advanced state than now, and they used to put it down +to laziness. + diff --git a/test/3-directives/putfiletext/putfile.6502 b/test/3-directives/putfiletext/putfile.6502 new file mode 100644 index 0000000..4c290af --- /dev/null +++ b/test/3-directives/putfiletext/putfile.6502 @@ -0,0 +1,13 @@ +\ PUTFILE - simple test + +ORG &2000 + +.start +.end + +SAVE "test", start, end + +\ Two parameter case handled by puttext.6502 +PUTFILE "PUT.TXT", &FEED, &BEAD +PUTFILE "PUT.TXT", "PUT", &BEEF +PUTFILE "PUT.TXT", "PUT2", &C0C0, &D0D0 diff --git a/test/3-directives/putfiletext/putfile.gold.ssd b/test/3-directives/putfiletext/putfile.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..839a4a57f060c60e4eefc8e843503a7a6c50483c GIT binary patch literal 8960 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 literal 0 HcmV?d00001 diff --git a/test/3-directives/putfilenonexistentdemo.fail.6502 b/test/3-directives/putfiletext/putfilenonexistentdemo.fail.6502 similarity index 100% rename from test/3-directives/putfilenonexistentdemo.fail.6502 rename to test/3-directives/putfiletext/putfilenonexistentdemo.fail.6502 diff --git a/test/3-directives/putfiletext/putsyntax1.fail.6502 b/test/3-directives/putfiletext/putsyntax1.fail.6502 new file mode 100644 index 0000000..9ab3d7d --- /dev/null +++ b/test/3-directives/putfiletext/putsyntax1.fail.6502 @@ -0,0 +1,5 @@ +\ PUTFILE - no params + +\ PUTTEXT uses the same parser, so this test is not repeated. + +PUTFILE diff --git a/test/3-directives/putfiletext/putsyntax2.fail.6502 b/test/3-directives/putfiletext/putsyntax2.fail.6502 new file mode 100644 index 0000000..49ac56a --- /dev/null +++ b/test/3-directives/putfiletext/putsyntax2.fail.6502 @@ -0,0 +1,5 @@ +\ PUTFILE - one param + +\ PUTTEXT uses the same parser, so this test is not repeated. + +PUTFILE "test" diff --git a/test/3-directives/putfiletext/putsyntax3.fail.6502 b/test/3-directives/putfiletext/putsyntax3.fail.6502 new file mode 100644 index 0000000..c023a18 --- /dev/null +++ b/test/3-directives/putfiletext/putsyntax3.fail.6502 @@ -0,0 +1,5 @@ +\ PUTFILE - two string params + +\ PUTTEXT uses the same parser, so this test is not repeated. + +PUTFILE "PUT", "PUT" diff --git a/test/3-directives/putfiletext/putsyntax4.fail.6502 b/test/3-directives/putfiletext/putsyntax4.fail.6502 new file mode 100644 index 0000000..b7c5392 --- /dev/null +++ b/test/3-directives/putfiletext/putsyntax4.fail.6502 @@ -0,0 +1,5 @@ +\ PUTFILE - no strings + +\ PUTTEXT uses the same parser, so this test is not repeated. + +PUTFILE &DECA diff --git a/test/3-directives/putfiletext/putsyntax5.fail.6502 b/test/3-directives/putfiletext/putsyntax5.fail.6502 new file mode 100644 index 0000000..584c5db --- /dev/null +++ b/test/3-directives/putfiletext/putsyntax5.fail.6502 @@ -0,0 +1,5 @@ +\ PUTFILE - extra param + +\ PUTTEXT uses the same parser, so this test is not repeated. + +PUTFILE "PUT", "PUT", &BADE, &FADE, 0 diff --git a/test/3-directives/putfiletext/puttext.6502 b/test/3-directives/putfiletext/puttext.6502 new file mode 100644 index 0000000..4ddbc74 --- /dev/null +++ b/test/3-directives/putfiletext/puttext.6502 @@ -0,0 +1,10 @@ +\ PUTTEXT - simple test + +ORG &2000 + +.start +.end + +SAVE "test", start, end + +PUTTEXT "PUT.TXT", &FEED diff --git a/test/3-directives/putfiletext/puttext.gold.ssd b/test/3-directives/putfiletext/puttext.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..fbcd35af7ca6028d8eebba5856da3fd18343c659 GIT binary patch literal 3328 zcmd^>O>5*v5Qa^13kdQPiebT*@qjNmWDmg$2|JKLAP$>cVtS;O)l92fboW?M{`3BY zB~P_vv-vpX6j(3o@krJ6@m9TW_0`q-^W)F=hx=ddUlp5+h`ib!e;OZOelG7ny#Mgq z7gwKM3IDG6{6v0^GMG+g<IlDe7fO^em?FuV&V|RD6yqqS8O$^-dl{_pWmsf1;UIF9 zHksLTE?$Zl>1><q<ciG6w&z3B9A&nl2tG&7*v_@aZ)e|Haal}|=sRhL%P6sz(FMEb zVJE${KI^IJ;<Md1`pn08=19@UC5Fe#W?RUe=O;~~43?ONkAx%drH@HMECaRdn|ZK- zta}sMMUK~_IPYIejyx+vygUwtzvg%$r7p_gs9YXn?4(>$kki5!{1Cj+cOlMzuzRO) zQG9F*v1SuW-tX{}jqCQZqsl3nwz$?}P1$+Syaw}Rf#lHeWF`%Hv=kPTeX*{fDkxSv z0#KC4*KD7_)KbWB30+d~ds*ToH8IgKlW0>L$1#S<n-+JbI1c1JVagye_=R6q$hFK- zI+v}<Hj6dwaM+3b6rbCcOEOL|xlFYu+02T`+8}V&0>5sW-8<_&-htsd*9MHH6blp@ zkIcT==_EW`1F)7$Po$W$Rp*+$6gSe(&YC2rNDcLL!L=feMGc@cLvNdpBLJev_pYx4 zbxO|mYleDetoctSGas{A<Ybu#OLLg{rgy>R!FC*f1GZaY8uBmk9!T}!A9dBJ>gUNe zTFWE<8KG=PPyvT?u$tF9$g*11ivP`Ok3nk!PMYV~ujRDOa?j+zMVtn$oJsqeH&=-A z?UPMQopFIS&}E1-V*&+^@}Ma`XnqI!a0ibVb|syTFm^S%&RCs=HgZg$Ff$5)@0BpL zI!-n)X_!3|k#Aj`1CLg;TYo7nVB!+rI<^Zk;Bu+28=6)f4Y<ZjjlwgSuj^*x<!o}e zE@FS5EPSEu7~epxHqIt>e!<I1KSHml(W=#kvfQW&W593`(=&j2C@Yd>O?{mxrYF0O z6nYTIs%Sa@cY2@#tRly-mbE-BcTTZjPe%;ow7E@gDlUdqm3r#MKWS_<c~oZCGFN1d zXjst)4zv)o*q)(!b*$ou->Q_I>*|Oo)L4^~_9>qAWEEvA0dgo@fHs2cHJnyAh{KkF z3kPkPN^BKy&aNga{4aJVQ|0;X%3&>-=nDPW+DU_dOxR!H5plH-Yh2~?c2wzq6r(!1 zz0|yWQm2=-b2rqJkRUM0=p#a-Dfeb{v}piX-=J0}gdedyZiGD%?d0oB#*=rCRtbsc zH&**!CF~_2<&T8O4UDWpqnoSJ_2XV?S`f(uGUj%(yA^*rAP2V3#hUXfH~L0yWdi(@ zs}hDms!#RJfB8`sF7lO1L(0IaK5nJha037+KdVbt8?--A-HXkMp<)VeZ*^g$X|oC{ zXq(L)Kz32vmdmB@UzAiwdXrD(ORj3R8nQ8fs?Y+(3RPx$JzB8>);adJPWJP%P=?mc z&ot$WS2pGexU>rhX)`X_`ktp%nHX-bGOVpv>}`6|rfR<-S}t|V)dd=v)8-gnx}-cn z;K-{*PSz!>Gx|LEp7BXHlR9W!YA@bYY14}fDK>c-rxHi9KG`J_P8X~)g#EIwLE3?} zz?^bx(C;ILTEmX@j<6-;8}^_JEz5>kjbs+N2c)yAgb9mKaYd}|dUhFbt4wMI^hh^L z;NDd4B&y{!82J?WE^&DR7I^EOGS`Le?!ko$MlfChmW(Eq1@(z5@9&g3bq6Z`+k&PK zDuHzj#M{2gsP=%HhgpbqKiEZSTt(!}-K16?^fxIYBHEVPBCy2%hwVC+TdoJKZS*w- y8Mv7Upc}J$(uQ=j4pmh@0q6MDj*{9UtULaS&jP0lq2a4Hf57Lg^Zx(8ME?nPG=O6O literal 0 HcmV?d00001 From 4a06386a583184aceb695da0d7cd0d695deca4b5 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 21:26:25 +0100 Subject: [PATCH 115/144] Missing EOL test in PUTFILE/PUTTEXT argument parser --- src/commands.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index b51ef50..7048a05 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1533,7 +1533,7 @@ void LineParser::HandlePutFileCommon( bool bText ) throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); } - if ( m_line[ m_column ] != ',' ) + if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) { throw AsmException_SyntaxError_MissingComma( m_line, m_column ); } @@ -1549,7 +1549,7 @@ void LineParser::HandlePutFileCommon( bool bText ) { beebFilename = string(beebFileOrStartAddr.GetString().Text(), beebFileOrStartAddr.GetString().Length()); - if ( m_line[ m_column ] != ',' ) + if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) { // did not find a comma throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); From f62b5cf96b34e26076e1c44121a839d5547af79d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 21:33:57 +0100 Subject: [PATCH 116/144] File name in test should have been in lower case --- test/3-directives/putfiletext/putfile.6502 | 6 +++--- .../3-directives/putfiletext/putfile.gold.ssd | Bin 8960 -> 8960 bytes test/3-directives/putfiletext/puttext.6502 | 2 +- .../3-directives/putfiletext/puttext.gold.ssd | Bin 3328 -> 3328 bytes 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/3-directives/putfiletext/putfile.6502 b/test/3-directives/putfiletext/putfile.6502 index 4c290af..643439d 100644 --- a/test/3-directives/putfiletext/putfile.6502 +++ b/test/3-directives/putfiletext/putfile.6502 @@ -8,6 +8,6 @@ ORG &2000 SAVE "test", start, end \ Two parameter case handled by puttext.6502 -PUTFILE "PUT.TXT", &FEED, &BEAD -PUTFILE "PUT.TXT", "PUT", &BEEF -PUTFILE "PUT.TXT", "PUT2", &C0C0, &D0D0 +PUTFILE "put.txt", &FEED, &BEAD +PUTFILE "put.txt", "PUT", &BEEF +PUTFILE "put.txt", "PUT2", &C0C0, &D0D0 diff --git a/test/3-directives/putfiletext/putfile.gold.ssd b/test/3-directives/putfiletext/putfile.gold.ssd index 839a4a57f060c60e4eefc8e843503a7a6c50483c..08c231e5573391a0f6edf2cce3cff4c03b11ce29 100644 GIT binary patch delta 18 ZcmZp0YjB$&!Cp{WqE}K;vQhq_5&%DG2KE2| delta 18 ZcmZp0YjB$&!5$DAq8AbovQhq_5&$`A1~vcy diff --git a/test/3-directives/putfiletext/puttext.6502 b/test/3-directives/putfiletext/puttext.6502 index 4ddbc74..e483e1a 100644 --- a/test/3-directives/putfiletext/puttext.6502 +++ b/test/3-directives/putfiletext/puttext.6502 @@ -7,4 +7,4 @@ ORG &2000 SAVE "test", start, end -PUTTEXT "PUT.TXT", &FEED +PUTTEXT "put.txt", &FEED diff --git a/test/3-directives/putfiletext/puttext.gold.ssd b/test/3-directives/putfiletext/puttext.gold.ssd index fbcd35af7ca6028d8eebba5856da3fd18343c659..1eb498321143af97bbb43faf24627acd1da1521b 100644 GIT binary patch delta 24 ZcmZpWYLMb*fP#Y361|d&l8yWyc>p-U1?T_( delta 24 ZcmZpWYLMb*fP#R~5WSFykd6Ewc>prO1t<Uj From f28977fbce47d1ef98a6b5e6f817c9bb1022552b Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 23 Jul 2021 22:41:39 +0100 Subject: [PATCH 117/144] Automatically create release when a 'v*' tag is pushed --- .github/workflows/actions.yml | 2 ++ .github/workflows/create-release.yml | 52 ++++++++++++++++++++++++++++ src/VS2010/build.cmd | 1 + src/VS2010/package.cmd | 2 ++ 4 files changed, 57 insertions(+) create mode 100644 .github/workflows/create-release.yml create mode 100644 src/VS2010/build.cmd create mode 100644 src/VS2010/package.cmd diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1d996b2..42f51f0 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,3 +1,5 @@ +name: Build and run tests + on: [push, pull_request] jobs: diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..84e5a8b --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,52 @@ +name: Create Release + +on: + push: + tags: + - 'v*' + +jobs: + create_release: + name: Create GitHub Release + runs-on: windows-2019 + defaults: + run: + working-directory: .\src\VS2010 + + steps: + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + + - uses: actions/checkout@v2 + + - name: Build Binary + shell: cmd + run: call .\build.cmd + + - name: Package Binary + shell: cmd + run: call .\package.cmd + + - name: Create Release + id: create_release + uses: actions/create-release@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + Automated Release by GitHub Action CI + draft: false + prerelease: true + + - name: Upload Release Asset (Windows x86) + id: upload-release-asset-x86 + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./src/VS2010/beebasm-win32.zip + asset_name: beebasm-win32.zip + asset_content_type: application/zip diff --git a/src/VS2010/build.cmd b/src/VS2010/build.cmd new file mode 100644 index 0000000..ce0fcf6 --- /dev/null +++ b/src/VS2010/build.cmd @@ -0,0 +1 @@ +msbuild /target:Build /property:Configuration=Release /property:Platform=Win32 BeebAsm.vcxproj diff --git a/src/VS2010/package.cmd b/src/VS2010/package.cmd new file mode 100644 index 0000000..e816e12 --- /dev/null +++ b/src/VS2010/package.cmd @@ -0,0 +1,2 @@ +@echo off +powershell Compress-Archive -Path "..\..\beebasm.exe" -DestinationPath ".\beebasm-win32.zip" From 3252f978bcfc611ef9f97271848fb2b285fc3d9d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 24 Jul 2021 13:06:56 +0100 Subject: [PATCH 118/144] GitHub actions - include Windows in build and test --- .github/workflows/actions.yml | 14 ++++++++++++++ src/VS2010/build.cmd | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/VS2010/build.cmd diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1d996b2..f0463c3 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,3 +1,5 @@ +name: Build and run tests + on: [push, pull_request] jobs: @@ -27,3 +29,15 @@ jobs: steps: - uses: actions/checkout@v2 - run: make -C src all VERBOSE=1 + msbuild: + runs-on: windows-2019 + name: Compile via msbuild on Windows + steps: + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + - uses: actions/checkout@v2 + - name: Build Binary + shell: cmd + working-directory: .\src\VS2010 + run: call .\build.cmd + - run: python test\testrunner.py diff --git a/src/VS2010/build.cmd b/src/VS2010/build.cmd new file mode 100644 index 0000000..b65add3 --- /dev/null +++ b/src/VS2010/build.cmd @@ -0,0 +1 @@ +msbuild /target:Build /property:Configuration=Release /property:Platform=Win32 BeebAsm.vcxproj From f7e4cbcd6e86d28dc6cc3fa27a2a4a3d896f874a Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 24 Jul 2021 13:12:11 +0100 Subject: [PATCH 119/144] GitHub actions - include verbose flag in Windows test --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f0463c3..778f792 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -40,4 +40,4 @@ jobs: shell: cmd working-directory: .\src\VS2010 run: call .\build.cmd - - run: python test\testrunner.py + - run: python test\testrunner.py -v From 79089d5104052652cacd83361138f8d0503666bb Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 26 Jul 2021 15:05:41 +0100 Subject: [PATCH 120/144] Tests for handling undefined symbols in directives --- test/3-directives/assertundef.6502 | 9 +++++++++ test/3-directives/putfiletext/putundef.6502 | 17 +++++++++++++++++ .../putfiletext/putundef.gold.ssd | Bin 0 -> 6144 bytes test/3-directives/save/saveundefexec.6502 | 18 ++++++++++++++++++ test/3-directives/save/saveundefexec.gold.ssd | Bin 0 -> 768 bytes 5 files changed, 44 insertions(+) create mode 100644 test/3-directives/assertundef.6502 create mode 100644 test/3-directives/putfiletext/putundef.6502 create mode 100644 test/3-directives/putfiletext/putundef.gold.ssd create mode 100644 test/3-directives/save/saveundefexec.6502 create mode 100644 test/3-directives/save/saveundefexec.gold.ssd diff --git a/test/3-directives/assertundef.6502 b/test/3-directives/assertundef.6502 new file mode 100644 index 0000000..9c6e966 --- /dev/null +++ b/test/3-directives/assertundef.6502 @@ -0,0 +1,9 @@ +\ ASSERT - don't complain about undefined symbols on first pass + +ASSERT(ABS(start)) + +ORG &2000 + +.start +SAVE "test", start, start + diff --git a/test/3-directives/putfiletext/putundef.6502 b/test/3-directives/putfiletext/putundef.6502 new file mode 100644 index 0000000..c99041f --- /dev/null +++ b/test/3-directives/putfiletext/putundef.6502 @@ -0,0 +1,17 @@ +\ PUTTEXT - undefined addresses + +\ no host filename and undefined start address +PUTTEXT "put.txt", start + +\ undefined exec address +PUTTEXT "put.txt", "put2", &2000, exec + +ORG &2000 + +.start +.end +NOP +.exec + +SAVE "test", start, end + diff --git a/test/3-directives/putfiletext/putundef.gold.ssd b/test/3-directives/putfiletext/putundef.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..0377c76c1f230c1dc45e408c9b8884ff365369ec GIT binary patch literal 6144 zcmeH~&5zqQ5XIg0RzUY02S$Lscnxe1Jv4`61GI>N_S6DFFI~_QWf7K0fuvVa|N8!h zk{9X6DW@QCf~?mPIUjH4y_sw_%b#ph7LnKEC4aYm?(>KIdVT$6f4ureHu`t7d9{(R z<j-$6m44^*>n}U;&E_DTX=F0}**4;Q4w5^QC0f%sf4Yw$3}ULzjN`nM&YC`Vvkb=X zMGjI&GxgjHmn=paTSq&(EK{`gd0$lrnXJ!(&4D|%akc5!yYH;HB*shV8>zd?Afc7P zdAs9bBdxW4(p%%DPj*-7vp$3~SF%3N!JjVc*-Y#_KWY%Uv-mW8L=5%4v>}QQxg(cd zHFee#b!&V*%i(Si*Y|HE1)k+DTuxo)w<%l*sR`0KGM7^bjpR%8a-8{s9h_JC&W8yQ zw(k`#k`Hyp*JON7yDfH-am`M)WI0AtXIEQJlXmJfuFgDLAlX+unFvEpmc&A|FQ*Hr z3X0W=02Jx*HQ8q{wItGAd=nM?R_1VtRftr~M9Nf$VF-TgO^rE|9T(CLH@TDG`<WkB zh&4|^8kej|Hi<QLx8I8V7OrJWIU1*!T*gw9WG2OADG)eof<IK%_Py<UtOLVcDg_u! z&K4-tACdjI)lRsc24GE>nn*ThtHw2Y$!?&YjWtn@fgH;1jA=!j6UBk{47II3_5g?? zzqqzE)G<2SE)nXTp~OF$M1M?XmZPN~EXASctJZm!I@@slE!eJssmp)(dm_}Q|KwG? zs-0(BX)Y)J(L%`%paKqOZ#Ay>kYzblGXB3)I}X&wW2CzJej~@Vms>gqE@ISc=5*S> zthtPrpPp@;OOFe*fi7K`XcH)KkS7iCN#om7hevosvn%Peg*jKeYmb#)r~}so3e%$y z_;)1?r4FO@bQ;I5MC4l&CeNb<?Yh6@8Za@5ZEf2b8E`3=*A+!8hB{1RrFh{P%-6BA z@^UiC-(|5MMhjmkJBRO~Rvji2n|{X1LO)zDs8Oor3~9MR6~=(!ET*La^-@wK^Ah^f zQFKpo4I#83kW|sM0Uq^20a!(jX3a}_n(h=s<~$WKkkh6*x-q-p7gcJ>7yE><(&VJf zE@>{v9MG_=4;*MAXrVqs^<r4X5xYex8`qQ;QK&ghOj@UK)|*9?t$4_xZ~@u?vUhM= z%^(hI1kUfZWGb;mz`45|S>S&;cQi$w-z^;0gz>J>pRFC$`NxF)10La5>#+D$KCf4m z{s%Ftlj}>3yCrmLSt@r=J`o85lY~AZG#YYi21l7XfbA>P>WJ_omWP$FN4y>T(v#uX zyOUKyV)?Dr`WFd%aY*_DE>Z;}i_qxgDs=t0RvH#WGM<RpZnh87ANR<CZBw@9yvU8d zkp~$8|LBT@aUhnb`sP#qC<|x#UZo)>U{xCrlC8f70F<B6CCeGK-;>?TnIla_7d|}b zz=-2|%B!HQXC48v30k(4FKzpxq}tM}d@5g3QM2VB69cF+B~YwTWxBUVD;B^S$K2M= zz77jzXqo(UQ@ZrZ#xw$#RskWchB?{3<!MnSn!8sS*3v8XHa=@nwcZdd<uc{U01fnM zb?`4$QXU|1#8o3F%aGL`y*7SN`^5WE4b(2ym);a<)5V1lt27T|4g*mi?Hq8Y16CQr zeAyKzb;nqsPuUvu`@liXVas?&*rMqx=AiR6!-ifBL>AZs;#pO~_*uxfAXa8QvkbUZ zCN%?Uq?5(7H<g`4wVVPYpCUg;mM36=x4l#5I=}87EL1Rp_VTb~Ffq@lPgr>WsLUxd zQ1M>}G<8r3ENvjYZHtU*4cI))jIZB2J1dQgh@9C?O6FdF5+Wj^Who^BOV0ngu4AcY zJ*aJ^uSv+j&4>e?n9Z{mq@i@Es@m}w$F5eC&}L!W@mshGoDPJFuf6#TJ}2$>&wqO^ wyFei#x!m^O+x~moe{cKmZU4RPzt{h;-uB;5S$x+!xBd6F|K9fB&v^^}8`wqnjsO4v literal 0 HcmV?d00001 diff --git a/test/3-directives/save/saveundefexec.6502 b/test/3-directives/save/saveundefexec.6502 new file mode 100644 index 0000000..9a7b399 --- /dev/null +++ b/test/3-directives/save/saveundefexec.6502 @@ -0,0 +1,18 @@ +\ SAVE - undefined symbol for execution address + +ORG &2000 + +.start + +NOP +LDA #'C' +JMP &FFEE + +.end + +SAVE "test", start, end, exec + +ORG &2001 + +.exec + diff --git a/test/3-directives/save/saveundefexec.gold.ssd b/test/3-directives/save/saveundefexec.gold.ssd new file mode 100644 index 0000000000000000000000000000000000000000..5f4e0337f2d4a6888c4765703338faa790edcb0b GIT binary patch literal 768 ycmZQzfPj+J;t~Y~1r;cFWKkT<3JeO23TzAvOe4$V3=FSUI{Uo)Khk`S@Gk(MG6u*1 literal 0 HcmV?d00001 From 5292a63007e76ba126765d961146ae34f1507ccf Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sun, 25 Jul 2021 14:22:16 +0100 Subject: [PATCH 121/144] ArgListParser simplifies argument list parsing --- src/commands.cpp | 879 ++++++++++++++++++++--------------------------- src/lineparser.h | 2 + 2 files changed, 374 insertions(+), 507 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 7048a05..df47814 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -138,6 +138,296 @@ int LineParser::GetTokenAndAdvanceColumn() +// Argument represents an attempt to parse an argument of type T from an argument list. +// Errors are mostly not thrown until the value is realised because at the time of +// parsing we don't know if the the value is optional or allowed to be undefined. +// For example, an optional string shouldn't immediately throw an error if it sees +// an undefined symbol because that may fit the next parameter. +template<class T> class Argument +{ +public: + typedef T ContainedType; + + enum State + { + // A value of type T was successfully parsed + StateFound, + // A value of the wrong type was available + StateTypeMismatch, + // A symbol is undefined + StateUndefined, + // No value was available (i.e. the end of the parameters) + StateMissing + }; + Argument(string line, int column, State state) : + m_line(line), m_column(column), m_state(state) + { + } + Argument(string line, int column, T value) : + m_line(line), m_column(column), m_state(StateFound), m_value(value) + { + } + // Is a value available? + bool Found() const + { + return m_state == StateFound; + } + // The column at which the parameter started. + int Column() const + { + return m_column; + } + // Extract the parameter value, throwing an exception if it didn't exist. + operator T() const + { + switch (m_state) + { + case StateFound: + return m_value; + case StateTypeMismatch: + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + case StateUndefined: + throw AsmException_SyntaxError_SymbolNotDefined( m_line, m_column ); + case StateMissing: + default: + throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); + } + } + // Check the parameter lies within a range. + Argument& Range(T mn, T mx) + { + if ( Found() && ( mn > m_value || m_value > mx ) ) + { + throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); + } + return *this; + } + // Check the parameter does not exceed a maximum. + Argument& Maximum(T mx) + { + if ( Found() && m_value > mx ) + { + throw AsmException_SyntaxError_NumberTooBig( m_line, m_column ); + } + return *this; + } + // Set a default value for optional parameters. + // This is overloaded for strings below. + Argument& Default(T value) + { + if ( !Found() ) + { + if ( m_state == StateUndefined) + { + throw AsmException_SyntaxError_SymbolNotDefined( m_line, m_column ); + } + m_value = value; + m_state = StateFound; + } + return *this; + } + // Permit this parameter to be an undefined symbol. + // This is overloaded for strings below. + Argument& AcceptUndef() + { + if (m_state == StateUndefined) + { + m_state = StateFound; + m_value = 0; + } + return *this; + } +private: + // Prevent copies + Argument operator=(const Argument& that); + + State m_state; + string m_line; + int m_column; + T m_value; +}; + +typedef Argument<int> IntArg; +typedef Argument<double> DoubleArg; +typedef Argument<string> StringArg; +typedef Argument<Value> ValueArg; + +StringArg& StringArg::Default(string value) +{ + if ( !Found() ) + { + m_value = value; + m_state = StateFound; + } + return *this; +} + +// AcceptUndef should not be called for string types. +StringArg& StringArg::AcceptUndef() +{ + assert(false); + if (m_state == StateUndefined) + { + throw AsmException_SyntaxError_SymbolNotDefined( m_line, m_column ); + } + return *this; +} + + +// ArgListParser helps parse a list of arguments. +class ArgListParser +{ +public: + ArgListParser(LineParser& lineParser, bool comma_first = false) : m_lineParser(lineParser) + { + m_first = !comma_first; + m_pending = false; + } + + IntArg ParseInt() + { + return ParseNumber<IntArg>(); + } + + DoubleArg ParseDouble() + { + return ParseNumber<DoubleArg>(); + } + + StringArg ParseString() + { + if ( !ReadPending() ) + { + return StringArg(m_lineParser.m_line, m_paramColumn, StringArg::StateMissing); + } + if ( m_pendingUndefined ) + { + return StringArg(m_lineParser.m_line, m_paramColumn, StringArg::StateUndefined); + } + if ( m_pendingValue.GetType() != Value::StringValue ) + { + return StringArg(m_lineParser.m_line, m_paramColumn, StringArg::StateTypeMismatch); + } + m_pending = false; + String temp = m_pendingValue.GetString(); + return StringArg(m_lineParser.m_line, m_paramColumn, string(temp.Text(), temp.Length())); + } + + ValueArg ParseValue() + { + if ( !ReadPending() ) + { + return ValueArg(m_lineParser.m_line, m_paramColumn, ValueArg::StateMissing); + } + if ( m_pendingUndefined ) + { + m_pending = false; + return ValueArg(m_lineParser.m_line, m_paramColumn, StringArg::StateUndefined); + } + m_pending = false; + return ValueArg(m_lineParser.m_line, m_paramColumn, m_pendingValue); + } + + void CheckComplete() + { + if ( m_pending ) + { + throw AsmException_SyntaxError_TypeMismatch( m_lineParser.m_line, m_lineParser.m_column ); + } + if ( m_lineParser.AdvanceAndCheckEndOfStatement() ) + { + throw AsmException_SyntaxError_InvalidCharacter( m_lineParser.m_line, m_lineParser.m_column ); + } + } +private: + // Prevent copies + ArgListParser(const ArgListParser& that); + ArgListParser operator=(const ArgListParser& that); + + template <class T> T ParseNumber() + { + if ( !ReadPending() ) + { + return T(m_lineParser.m_line, m_paramColumn, T::StateMissing); + } + if ( m_pendingUndefined ) + { + m_pending = false; + return T(m_lineParser.m_line, m_paramColumn, T::StateUndefined); + } + if ( m_pendingValue.GetType() != Value::NumberValue ) + { + return T(m_lineParser.m_line, m_paramColumn, T::StateTypeMismatch); + } + m_pending = false; + return T(m_lineParser.m_line, m_paramColumn, static_cast<T::ContainedType>(m_pendingValue.GetNumber())); + } + + // Return true if an argument is available + bool ReadPending() + { + if (!m_pending) + { + bool found = MoveNext(); + m_paramColumn = m_lineParser.m_column; + if (found) + { + try + { + m_pendingUndefined = false; + m_pendingValue = m_lineParser.EvaluateExpression(); + } + catch ( AsmException_SyntaxError_SymbolNotDefined& ) + { + if ( !GlobalData::Instance().IsFirstPass() ) + { + throw; + } + m_pendingUndefined = true; + m_pendingValue = 0; + } + m_pending = true; + } + } + return m_pending; + } + + // Return true if there's something to parse + bool MoveNext() + { + if (m_first) + { + m_first = false; + return m_lineParser.AdvanceAndCheckEndOfStatement(); + } + else + { + if ( m_lineParser.AdvanceAndCheckEndOfStatement() ) + { + // If there's anything of interest it must be a comma + if ( m_lineParser.m_column >= m_lineParser.m_line.length() || m_lineParser.m_line[ m_lineParser.m_column ] != ',' ) + { + // did not find a comma + throw AsmException_SyntaxError_InvalidCharacter( m_lineParser.m_line, m_lineParser.m_column ); + } + m_lineParser.m_column++; + StringUtils::EatWhitespace( m_lineParser.m_line, m_lineParser.m_column ); + return true; + } + return false; + } + } + + LineParser& m_lineParser; + int m_paramColumn; + bool m_first; + bool m_pending; + bool m_pendingUndefined; + Value m_pendingValue; +}; + + + /*************************************************************************************************/ /** LineParser::HandleDefineLabel() @@ -282,20 +572,12 @@ void LineParser::HandleDirective() /*************************************************************************************************/ void LineParser::HandleOrg() { - int newPC = EvaluateExpressionAsInt(); - if ( newPC < 0 || newPC > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + ArgListParser args(*this); + int newPC = args.ParseInt().Range(0, 0xFFFF); + args.CheckComplete(); ObjectCode::Instance().SetPC( newPC ); SymbolTable::Instance().ChangeSymbol( "P%", newPC ); - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } @@ -307,19 +589,11 @@ void LineParser::HandleOrg() /*************************************************************************************************/ void LineParser::HandleCpu() { - int newCpu = EvaluateExpressionAsInt(); - if ( newCpu < 0 || newCpu > 1 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + ArgListParser args(*this); + int newCpu = args.ParseInt().Range(0, 1); + args.CheckComplete(); ObjectCode::Instance().SetCPU( newCpu ); - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } @@ -331,19 +605,11 @@ void LineParser::HandleCpu() /*************************************************************************************************/ void LineParser::HandleGuard() { - int val = EvaluateExpressionAsInt(); - if ( val < 0 || val > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + ArgListParser args(*this); + int val = args.ParseInt().Range(0, 0xFFFF); + args.CheckComplete(); ObjectCode::Instance().SetGuard( val ); - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } @@ -355,33 +621,14 @@ void LineParser::HandleGuard() /*************************************************************************************************/ void LineParser::HandleClear() { - int start = EvaluateExpressionAsInt(); - if ( start < 0 || start > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + ArgListParser args(*this); - m_column++; + int start = args.ParseInt().Range(0, 0xFFFF); + int end = args.ParseInt().Range(0, 0x10000); - int end = EvaluateExpressionAsInt(); - if ( end < 0 || end > 0x10000 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + args.CheckComplete(); ObjectCode::Instance().Clear( start, end ); - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } @@ -395,48 +642,18 @@ void LineParser::HandleMapChar() { // get parameters - either 2 or 3 - int param3 = -1; - int param1 = EvaluateExpressionAsInt(); - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - int param2 = EvaluateExpressionAsInt(); - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - m_column++; - - param3 = EvaluateExpressionAsInt(); - } - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } + ArgListParser args(*this); - // range checks + int param1 = args.ParseInt().Range(0x20, 0x7E); + int param2 = args.ParseInt().Range(0, 0xFF); + IntArg param3 = args.ParseInt().Range(0, 0xFF); - if ( param1 < 32 || param1 > 126 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + args.CheckComplete(); - if ( param3 == -1 ) + if ( !param3.Found() ) { // two parameters - if ( param2 < 0 || param2 > 255 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - // do single character remapping ObjectCode::Instance().SetMapping( param1, param2 ); } @@ -444,12 +661,7 @@ void LineParser::HandleMapChar() { // three parameters - if ( param2 < 32 || param2 > 126 || param2 < param1 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - if ( param3 < 0 || param3 > 255 ) + if ( param2 < 0x20 || param2 > 0x7E || param2 < param1 ) { throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); } @@ -553,17 +765,13 @@ void LineParser::HandleSkip() /*************************************************************************************************/ void LineParser::HandleSkipTo() { - int oldColumn = m_column; - - int addr = EvaluateExpressionAsInt(); - if ( addr < 0 || addr > 0x10000 ) - { - throw AsmException_SyntaxError_BadAddress( m_line, oldColumn ); - } + ArgListParser args(*this); + IntArg addr = args.ParseInt().Range(0, 0x10000); + args.CheckComplete(); if ( ObjectCode::Instance().GetPC() > addr ) { - throw AsmException_SyntaxError_BackwardsSkip( m_line, oldColumn ); + throw AsmException_SyntaxError_BackwardsSkip( m_line, addr.Column() ); } while ( ObjectCode::Instance().GetPC() < addr ) @@ -579,12 +787,6 @@ void LineParser::HandleSkipTo() throw; } } - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } @@ -655,31 +857,12 @@ void LineParser::HandleIncBin() /*************************************************************************************************/ void LineParser::HandleEqub() { - do - { - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - Value value; + ArgListParser args(*this); - try - { - value = EvaluateExpression(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsFirstPass() ) - { - value = 0; - } - else - { - throw; - } - } + Value value = args.ParseValue().AcceptUndef(); + do + { if (value.GetType() == Value::StringValue) { // handle equs @@ -720,19 +903,15 @@ void LineParser::HandleEqub() assert(false); } - if ( !AdvanceAndCheckEndOfStatement() ) - { + ValueArg arg = args.ParseValue().AcceptUndef(); + if (!arg.Found()) break; - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - m_column++; + value = arg; } while ( true ); + + args.CheckComplete(); } @@ -794,31 +973,12 @@ void LineParser::HandleEqus( const String& equs ) /*************************************************************************************************/ void LineParser::HandleEquw() { - do - { - int value; + ArgListParser args(*this); - try - { - value = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsFirstPass() ) - { - value = 0; - } - else - { - throw; - } - } - - if ( value > 0xFFFF ) - { - throw AsmException_SyntaxError_NumberTooBig( m_line, m_column ); - } + int value = args.ParseInt().AcceptUndef().Maximum(0xFFFF); + do + { if ( GlobalData::Instance().ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; @@ -840,24 +1000,15 @@ void LineParser::HandleEquw() throw; } - if ( !AdvanceAndCheckEndOfStatement() ) - { + IntArg arg = args.ParseInt().AcceptUndef().Maximum(0xFFFF); + if (!arg.Found()) break; - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } + value = arg; } while ( true ); + + args.CheckComplete(); } @@ -869,26 +1020,12 @@ void LineParser::HandleEquw() /*************************************************************************************************/ void LineParser::HandleEqud() { - do - { - unsigned int value; + ArgListParser args(*this); - try - { - value = EvaluateExpressionAsUnsignedInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsFirstPass() ) - { - value = 0; - } - else - { - throw; - } - } + int value = args.ParseInt().AcceptUndef(); + do + { if ( GlobalData::Instance().ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; @@ -914,24 +1051,15 @@ void LineParser::HandleEqud() throw; } - if ( !AdvanceAndCheckEndOfStatement() ) - { + IntArg arg = args.ParseInt().AcceptUndef(); + if (!arg.Found()) break; - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } + value = arg; } while ( true ); + + args.CheckComplete(); } @@ -1003,126 +1131,28 @@ void LineParser::HandleAssert() /*************************************************************************************************/ void LineParser::HandleSave() { - string saveFile; - int start = 0; - int end = 0; - int exec = 0; - int reload = 0; - - int oldColumn = m_column; - // syntax is SAVE ["filename"], start, end [, exec [, reload] ] - Value saveOrStart = EvaluateExpression(); - - if (saveOrStart.GetType() == Value::NumberValue) - { - start = static_cast<int>(saveOrStart.GetNumber()); - } - else if (saveOrStart.GetType() == Value::StringValue) - { - saveFile = string(saveOrStart.GetString().Text(), saveOrStart.GetString().Length()); - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + ArgListParser args(*this); - m_column++; + StringArg saveParam = args.ParseString(); + int start = args.ParseInt().Range(0, 0xFFFF); + int end = args.ParseInt().Range(0, 0x10000); + int exec = args.ParseInt().AcceptUndef().Default(start).Range(0, 0xFFFFFF); + int reload = args.ParseInt().Default(start).Range(0, 0xFFFFFF); + args.CheckComplete(); - // Get start address - start = EvaluateExpressionAsInt(); - } - else - { - assert(false); - throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); - } - - exec = start; - reload = start; - - if ( start < 0 || start > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - // get end address - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - end = EvaluateExpressionAsInt(); - - if ( end < 0 || end > 0x10000 ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - // get optional exec address - // we allow this to be a forward define as it needn't be within the block we actually save - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - m_column++; - - try - { - exec = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsSecondPass() ) - { - throw; - } - } - - if ( exec < 0 || exec > 0xFFFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - // get optional reload address - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - m_column++; - - reload = EvaluateExpressionAsInt(); - - if ( reload < 0 || reload > 0xFFFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - } - } - - // expect no more - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } - - if ( saveFile == "" ) + if ( !saveParam.Found() ) { if ( GlobalData::Instance().GetOutputFile() != NULL ) { - saveFile = GlobalData::Instance().GetOutputFile(); + saveParam.Default(GlobalData::Instance().GetOutputFile()); if ( GlobalData::Instance().IsSecondPass() ) { if ( GlobalData::Instance().GetNumAnonSaves() > 0 ) { - throw AsmException_SyntaxError_OnlyOneAnonSave( m_line, oldColumn ); + throw AsmException_SyntaxError_OnlyOneAnonSave( m_line, saveParam.Column() ); } else { @@ -1132,10 +1162,12 @@ void LineParser::HandleSave() } else { - throw AsmException_SyntaxError_NoAnonSave( m_line, oldColumn ); + throw AsmException_SyntaxError_NoAnonSave( m_line, saveParam.Column() ); } } + string saveFile = saveParam; + if ( GlobalData::Instance().ShouldOutputAsm() ) { cout << "Saving file '" << saveFile << "'" << endl; @@ -1214,70 +1246,15 @@ void LineParser::HandleFor() throw AsmException_SyntaxError_LabelAlreadyDefined( m_line, oldColumn ); } - // look for first comma + ArgListParser args(*this, true); + double start = args.ParseDouble(); + double end = args.ParseDouble(); + double step = args.ParseDouble().Default(1); + args.CheckComplete(); - if ( !AdvanceAndCheckEndOfStatement() ) + if ( step == 0.0 ) { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - m_column++; - - // look for start value - - double start = EvaluateExpressionAsDouble(); - - // look for comma - - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - // look for end value - - double end = EvaluateExpressionAsDouble(); - - double step = 1.0; - - if ( AdvanceAndCheckEndOfStatement() ) - { - // look for step variable - - if ( m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - step = EvaluateExpressionAsDouble(); - - if ( step == 0.0 ) - { - throw AsmException_SyntaxError_BadStep( m_line, m_column ); - } - - // check this is now the end - - if ( AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - + throw AsmException_SyntaxError_BadStep( m_line, m_column ); } m_sourceCode->AddFor( symbolName, @@ -1523,94 +1500,14 @@ void LineParser::HandlePutFileCommon( bool bText ) // Syntax: // PUTFILE/PUTTEXT <host filename>, [<beeb filename>,] <start addr> [,<exec addr>] - string hostFilename = EvaluateExpressionAsString(); - string beebFilename = hostFilename; - int start = 0; - int exec = 0; - - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_MissingComma( m_line, m_column ); - } - - m_column++; - - Value beebFileOrStartAddr = EvaluateExpression(); - if (beebFileOrStartAddr.GetType() == Value::NumberValue) - { - start = static_cast<int>(beebFileOrStartAddr.GetNumber()); - } - else if (beebFileOrStartAddr.GetType() == Value::StringValue) - { - beebFilename = string(beebFileOrStartAddr.GetString().Text(), beebFileOrStartAddr.GetString().Length()); - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - // Get start address - try - { - start = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsSecondPass() ) - { - throw; - } - } - } - else - { - assert(false); - throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); - } - - exec = start; - - if ( start < 0 || start > 0xFFFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + ArgListParser args(*this); - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - m_column++; + string hostFilename = args.ParseString(); + string beebFilename = args.ParseString().Default(hostFilename); + int start = args.ParseInt().AcceptUndef().Range(0, 0xFFFFFF); + int exec = args.ParseInt().AcceptUndef().Default(start).Range(0, 0xFFFFFF); - try - { - exec = EvaluateExpressionAsInt(); - } - catch ( AsmException_SyntaxError_SymbolNotDefined& ) - { - if ( GlobalData::Instance().IsSecondPass() ) - { - throw; - } - } - - if ( exec < 0 || exec > 0xFFFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - } - - // check this is now the end - - if ( AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + args.CheckComplete(); if ( GlobalData::Instance().IsSecondPass() ) { @@ -1876,39 +1773,13 @@ void LineParser::HandleError() /*************************************************************************************************/ void LineParser::HandleCopyBlock() { - int start = EvaluateExpressionAsInt(); - if ( start < 0 || start > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + ArgListParser args(*this); - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + int start = args.ParseInt().Range(0, 0xFFFF); + int end = args.ParseInt().Range(0, 0xFFFF); + int dest = args.ParseInt().Range(0, 0xFFFF); - m_column++; - - int end = EvaluateExpressionAsInt(); - if ( end < 0 || end > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - // did not find a comma - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - int dest = EvaluateExpressionAsInt(); - if ( dest < 0 || dest > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + args.CheckComplete(); try { @@ -1920,12 +1791,6 @@ void LineParser::HandleCopyBlock() e.SetColumn( m_column ); throw; } - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) - { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); - } } diff --git a/src/lineparser.h b/src/lineparser.h index 356284c..908841c 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -252,6 +252,8 @@ class LineParser Operator m_operatorStack[ MAX_OPERATORS ]; int m_valueStackPtr; int m_operatorStackPtr; + + friend class ArgListParser; }; From 2f7263e52b8b9b3571c178e967b708c65effb309 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 26 Jul 2021 16:54:41 +0100 Subject: [PATCH 122/144] Remove beebasm.exe, fix line-ending clash --- beebasm.exe | Bin 217088 -> 0 bytes src/VS2010/build.cmd | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 beebasm.exe diff --git a/beebasm.exe b/beebasm.exe deleted file mode 100644 index f77e25c09d54089a31309eca2fb28859bb318519..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217088 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≠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 M;NKGXzb%3P2P}>`HUIzs diff --git a/src/VS2010/build.cmd b/src/VS2010/build.cmd index ce0fcf6..b65add3 100644 --- a/src/VS2010/build.cmd +++ b/src/VS2010/build.cmd @@ -1 +1 @@ -msbuild /target:Build /property:Configuration=Release /property:Platform=Win32 BeebAsm.vcxproj +msbuild /target:Build /property:Configuration=Release /property:Platform=Win32 BeebAsm.vcxproj From eeb7d28c4561214a81282d1e780a348b08e92d6c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Mon, 26 Jul 2021 22:31:23 +0100 Subject: [PATCH 123/144] Fix template<> and typename errors and warnings for gcc --- src/commands.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index df47814..681ee21 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -167,6 +167,14 @@ template<class T> class Argument m_line(line), m_column(column), m_state(StateFound), m_value(value) { } + Argument(const Argument<T>& that) : + m_line(that.m_line), m_column(that.m_column), m_state(that.m_state), m_value(that.m_value) + { + m_line = that.m_line; + m_column = that.m_column; + m_state = that.m_state; + m_value = that.m_value; + } // Is a value available? bool Found() const { @@ -194,7 +202,7 @@ template<class T> class Argument } } // Check the parameter lies within a range. - Argument& Range(T mn, T mx) + Argument<T>& Range(T mn, T mx) { if ( Found() && ( mn > m_value || m_value > mx ) ) { @@ -203,7 +211,7 @@ template<class T> class Argument return *this; } // Check the parameter does not exceed a maximum. - Argument& Maximum(T mx) + Argument<T>& Maximum(T mx) { if ( Found() && m_value > mx ) { @@ -213,7 +221,7 @@ template<class T> class Argument } // Set a default value for optional parameters. // This is overloaded for strings below. - Argument& Default(T value) + Argument<T>& Default(T value) { if ( !Found() ) { @@ -228,7 +236,7 @@ template<class T> class Argument } // Permit this parameter to be an undefined symbol. // This is overloaded for strings below. - Argument& AcceptUndef() + Argument<T>& AcceptUndef() { if (m_state == StateUndefined) { @@ -238,12 +246,12 @@ template<class T> class Argument return *this; } private: - // Prevent copies - Argument operator=(const Argument& that); + // Prevent assignment + Argument<T> operator=(const Argument<T>& that); - State m_state; string m_line; int m_column; + State m_state; T m_value; }; @@ -252,7 +260,7 @@ typedef Argument<double> DoubleArg; typedef Argument<string> StringArg; typedef Argument<Value> ValueArg; -StringArg& StringArg::Default(string value) +template<> StringArg& StringArg::Default(string value) { if ( !Found() ) { @@ -263,7 +271,7 @@ StringArg& StringArg::Default(string value) } // AcceptUndef should not be called for string types. -StringArg& StringArg::AcceptUndef() +template<> StringArg& StringArg::AcceptUndef() { assert(false); if (m_state == StateUndefined) @@ -360,7 +368,7 @@ class ArgListParser return T(m_lineParser.m_line, m_paramColumn, T::StateTypeMismatch); } m_pending = false; - return T(m_lineParser.m_line, m_paramColumn, static_cast<T::ContainedType>(m_pendingValue.GetNumber())); + return T(m_lineParser.m_line, m_paramColumn, static_cast<typename T::ContainedType>(m_pendingValue.GetNumber())); } // Return true if an argument is available From 9c01bc74aae406053b035785e9ceb07c22a0f769 Mon Sep 17 00:00:00 2001 From: Julie Montoya <bluerizlagirl@gmail.com> Date: Mon, 2 Aug 2021 18:38:16 +0100 Subject: [PATCH 124/144] add jump_table_at_end.6502 (#61) --- examples/jump_table_at_end.6502 | 118 ++++++++++++++++++++++++++++++++ examples/jump_table_at_end.md | 55 +++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 examples/jump_table_at_end.6502 create mode 100644 examples/jump_table_at_end.md diff --git a/examples/jump_table_at_end.6502 b/examples/jump_table_at_end.6502 new file mode 100644 index 0000000..d8888e5 --- /dev/null +++ b/examples/jump_table_at_end.6502 @@ -0,0 +1,118 @@ +\\ EXAMPLE CODE SHOWING HOW TO ALIGN A BLOCK OF CODE TO *FINISH* ON +\\ A PAGE BOUNDARY. +\\ +\\ DEDICATED TO THE PUBLIC DOMAIN BY JULIE KIRSTY LOUISE MONTOYA, 2021 +\\ +\\ THIS CREATES A JUMP TABLE WITH THREE FIXED ENTRY POINTS: +\\ +\\ wibble = &xxF7 +\\ blah = &xxFA +\\ sayXY = &xxFD +\\ +\\ YOU COULD ALSO USE THIS TO ALLOCATE A BLOCK OF MEMORY FOR PASSING +\\ PARAMETERS BETWEEN BASIC AND ASSEMBLER. + + +\\ MOS ENTRY POINTS + +osasci = &FFE3 + + +\\ WE WANT OUR CODE TO BEGIN JUST BELOW THE START OF SCREEN MEMORY +\\ +\\ MODE 0, 1, 2 ; ORG &2F00 +\\ MODE 3 ; ORG &3F00 +\\ MODE 4, 5 ; ORG &5700 +\\ MODE 6 ; ORG &5F00 +\\ MODE 7 ; ORG &7B00 + +ORG &7B00 + +._begin + +\\ THE ACTUAL CODE + +\\ PRINT THE WORD "WIBBLE" AND START A NEW LINE + +.real_wibble + LDX #wibble_msg MOD 256 + LDY #wibble_msg DIV 256 + JMP real_sayXY + +\\ PRINT THE WORD "BLAH" AND START A NEW LINE + +.real_blah + LDX #blah_msg MOD 256 + LDY #blah_msg DIV 256 + +\\ PRINT A STRING OF UP TO 255 CHARACTERS OF TEXT FROM ANYWHERE IN MEMORY +\\ X => UNITS BYTE OF STARTING ADDRESS +\\ Y => 256ES BYTE OF STARTING ADDRESS +\\ RETURNS AFTER PRINTING ANY CONTROL CHARACTER, INCLUDING CR, BRK + +.real_sayXY + STX &70 \ Store address of text in zero page, + STY &71 \ low byte before high byte +._sayXY0 + LDY #0 \ Initialise Y index register +._sayXY1 + LDA (&70),Y \ Read a character of text from memory + INY \ Move on to next character + JSR osasci \ Print the character + CMP #32 \ See if ASCII code was less than 32 (space) + BCS _sayXY1 \ Go round again if greater than or equal to 32 +._rts + RTS + +.wibble_msg + EQUS "WIBBLE": EQUB 13 +.blah_msg + EQUS "BLAH": EQUB 13 + + +\\ THAT'S ALL THIS LITTLE DEMO DOES. +\\ THE JUMP TABLE WILL ALWAYS BE IN A FIXED LOCATION AT THE END. YOUR +\\ CODE HAS ALL THE SPACE IN BETWEEN TO GROW INTO. + +\\ THE END OF THE CODE PROPER, AND THE BEGINNING OF THE MAGIC + +._real_end \ This is the first location after end of code + +ALIGN &100 \ Now P% is on a page boundary + +GUARD P% \ Prevent assembling any more code after here + +CLEAR _real_end, P% \ Mark the area from the real end of code as far + \ as the new origin, as available for use again + +ORG P% - 9 \ Wind the origin back 3 bytes for each entry in + \ the jump table, to &xxF7 + \ (YOU WILL NEED TO EDIT THIS TO SUIT YOUR CODE!) + +\\ THE END OF THE MAGIC, AND THE BEGINNING OF THE JUMP TABLE +\\ +\\ THIS IS SIMPLY A SERIES OF JMP INSTRUCTIONS INTO THE MAIN CODE +\\ EACH ONE IS EXACTLY 3 BYTES LONG, HENCE THE 9 ABOVE + +.wibble + JMP real_wibble +.blah + JMP real_blah +.sayXY + JMP real_sayXY + +\\ THE END OF THE JUMP TABLE +\\ +\\ WE WILL ALREADY HAVE STOPPED WITH AN ERROR IF THE JUMP TABLE HAS +\\ EXCEEDED THE PAGE BOUNDARY. BUT YOU CAN UNCOMMENT THE FOLLOWING LINE +\\ IF YOU WISH ALSO TO STOP WITH AN ERROR IF THE JUMP TABLE HAS FALLEN +\\ SHORT OF THE PAGE BOUNDARY; + +\ASSERT ((P% MOD 256) = 0) + +._end + +\\ SAVE THE CODE. USING THE ADDRESS OF AN RTS INSTRUCTION FOR THE +\\ EXECUTION ADDRESS MEANS IT CAN SAFELY BE *RUN. + +SAVE "M.JT1", _begin, _end, _rts diff --git a/examples/jump_table_at_end.md b/examples/jump_table_at_end.md new file mode 100644 index 0000000..8a2a10c --- /dev/null +++ b/examples/jump_table_at_end.md @@ -0,0 +1,55 @@ +# jump_table_at_end.6502 + +This is a simple example showing how to align a jump table or parameter +block to appear just _before_ a page boundary, so you can have something +like this: +``` +.wibble + 7BF7 4C 00 7B JMP real_wibble +.blah + 7BFA 4C 07 7B JMP real_blah +.sayXY + 7BFD 4C 0B 7B JMP real_sayXY +``` +This is enormously useful when you have a BASIC program which is `CALL`ing +machine code routines; if you follow this example then you should never +have to change the entry points used by your BASIC program, no matter how +the machine code grows. The only changes you will need to make will be to +add any new entry points along with the BASIC that is going to make use of +them; and to alter `HIMEM` as your machine code grows (otherwise, the +BASIC `PROC` stack will stomp on your code). + +The trick is this: We create a label pointing to the first free location +after the code that has been assembled so far. Then we `ALIGN &100` to +advance the origin to the next page boundary. We use `CLEAR` to tell +BeebAsm that the area from where the aforementioned label points to the +current origin is safe to use; and lastly, we adjust the origin backwards +by the exact size of the code or data we are going to insert. + +This is the actual code from the example, with its nine-byte jump table: +``` +._real_end \ This is the first location after end of code + +ALIGN &100 \ Now P% is on a page boundary + +GUARD P% \ Prevent assembling any more code after here + +CLEAR _real_end, P% \ Mark the area from the real end of code as far + \ as the new origin, as available for use again + +ORG P% - 9 \ Wind the origin back 3 bytes for each entry in + \ the jump table, to &xxF7 + \ (YOU WILL NEED TO EDIT THIS TO SUIT YOUR CODE!) +``` +The value 9 will need to be edited to match the size of the fixed part of +the code in your own use case. You can just decrease the origin, if the +code grows too big. + +You can optionally add the following after your jump table: +``` +ASSERT ((P% MOD 256) = 0) +``` +This will cause BeebAsm to stop with an error if the jump table has fallen +short of the page boundary. (It will of course already have stopped with an +error if the jump table has exceeded the page boundary, thanks to the +earlier `GUARD` directive.) From 5318636ef5a9f00bbff1dcb7889712b37667e38d Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 19 Feb 2022 21:35:52 +0000 Subject: [PATCH 125/144] Issue #66 - Handle out of range integer conversions Doubles in the range -2147483648 to 4294967295 are converted to integers and everything else throws an OutOfIntegerRange exception. All double to int casts have been replaced with a call to ConvertDoubleToInt. This should behave the same on ARM and x86. --- src/asmexception.h | 1 + src/expression.cpp | 70 +++++++++++++++++++------ src/lineparser.h | 2 + test/2-expressions/intrangehi.fail.6502 | 2 + test/2-expressions/intrangelo.fail.6502 | 2 + test/2-expressions/operators.6502 | 6 +++ 6 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 test/2-expressions/intrangehi.fail.6502 create mode 100644 test/2-expressions/intrangelo.fail.6502 diff --git a/src/asmexception.h b/src/asmexception.h index 243218a..7789a9f 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -251,6 +251,7 @@ DEFINE_SYNTAX_EXCEPTION( BackwardsSkip, "Attempted to skip backwards to an addre DEFINE_SYNTAX_EXCEPTION( NoAnonSave, "Cannot specify SAVE without a filename if no default output filename has been specified." ); DEFINE_SYNTAX_EXCEPTION( OnlyOneAnonSave, "Can only use SAVE without a filename once per project." ); DEFINE_SYNTAX_EXCEPTION( TypeMismatch, "Type mismatch." ); +DEFINE_SYNTAX_EXCEPTION( OutOfIntegerRange, "Number out of range for a 32-bit integer." ); diff --git a/src/expression.cpp b/src/expression.cpp index bf9fd1b..0684bbb 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -660,7 +660,7 @@ double LineParser::EvaluateExpressionAsDouble( bool bAllowOneMismatchedCloseBrac /*************************************************************************************************/ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< int >( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); + return ConvertDoubleToInt( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); } @@ -673,7 +673,7 @@ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) /*************************************************************************************************/ unsigned int LineParser::EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< unsigned int >( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); + return static_cast< unsigned int >( ConvertDoubleToInt( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ) ); } @@ -763,6 +763,19 @@ double LineParser::StackTopNumber() } +/*************************************************************************************************/ +/** + LineParser::StackTopInt() + + Retrieve a number from the top of the stack and convert to an int, or throw an exception +*/ +/*************************************************************************************************/ +int LineParser::StackTopInt() +{ + return ConvertDoubleToInt( StackTopNumber() ); +} + + /*************************************************************************************************/ /** LineParser::StackTopTwoNumbers() @@ -796,9 +809,34 @@ std::pair<double, double> LineParser::StackTopTwoNumbers() std::pair<int, int> LineParser::StackTopTwoInts() { std::pair<double, double> pair = StackTopTwoNumbers(); - return std::pair<int, int>(static_cast<int>(pair.first), static_cast<int>(pair.second)); + return std::pair<int, int>(ConvertDoubleToInt(pair.first), ConvertDoubleToInt(pair.second)); } +/*************************************************************************************************/ +/** + LineParser::ConvertDoubleToInt() + + Convert a double to an int throwing an exception if it is out of range + + Handle values between -2147483648 and 4294967295. +*/ +/*************************************************************************************************/ +int LineParser::ConvertDoubleToInt(double value) +{ + if ((value < INT_MIN) || (value > UINT_MAX)) + { + throw AsmException_SyntaxError_OutOfIntegerRange( m_line, m_column ); + } + + if (value <= INT_MAX) + { + return static_cast<int>( value ); + } + else + { + return static_cast<unsigned int>( value ); + } +} /*************************************************************************************************/ /** @@ -1167,7 +1205,7 @@ void LineParser::EvalNegate() /*************************************************************************************************/ void LineParser::EvalNot() { - int value = ~static_cast<int>(StackTopNumber()); + int value = ~StackTopInt(); m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1196,7 +1234,7 @@ void LineParser::EvalPosate() /*************************************************************************************************/ void LineParser::EvalLo() { - int value = static_cast<int>(StackTopNumber()) & 0xFF; + int value = StackTopInt() & 0xFF; m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1209,7 +1247,7 @@ void LineParser::EvalLo() /*************************************************************************************************/ void LineParser::EvalHi() { - int value = (static_cast<int>(StackTopNumber()) & 0xffff) >> 8; + int value = (StackTopInt() & 0xffff) >> 8; m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1401,8 +1439,7 @@ void LineParser::EvalRadToDeg() /*************************************************************************************************/ void LineParser::EvalInt() { - m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( - static_cast< int >( StackTopNumber() ) ); + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( StackTopInt() ); } @@ -1452,7 +1489,7 @@ void LineParser::EvalRnd() } else { - result = static_cast< double >( static_cast< int >( beebasm_rand() / ( static_cast< double >( BEEBASM_RAND_MAX ) + 1.0 ) * val ) ); + result = static_cast< double >( ConvertDoubleToInt( beebasm_rand() / ( static_cast< double >( BEEBASM_RAND_MAX ) + 1.0 ) * val ) ); } m_valueStack[ m_valueStackPtr - 1 ] = result; @@ -1514,7 +1551,7 @@ void LineParser::EvalStr() void LineParser::EvalStrHex() { ostringstream stream; - stream << std::hex << std::uppercase << static_cast<int>(StackTopNumber()); + stream << std::hex << std::uppercase << StackTopInt(); string result = stream.str(); m_valueStack[ m_valueStackPtr - 1 ] = String(result.data(), result.length()); @@ -1569,8 +1606,7 @@ void LineParser::EvalLen() /*************************************************************************************************/ void LineParser::EvalChr() { - double value = StackTopNumber(); - int ascii = static_cast<int>(value); + int ascii = StackTopInt(); if ((ascii < 0) || (ascii > 255)) { throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); @@ -1618,8 +1654,8 @@ void LineParser::EvalMid() m_valueStackPtr -= 2; String text = value1.GetString(); - int index = static_cast<int>(value2.GetNumber()) - 1; - int length = static_cast<int>(value3.GetNumber()); + int index = ConvertDoubleToInt(value2.GetNumber()) - 1; + int length = ConvertDoubleToInt(value3.GetNumber()); if ((index < 0) || (static_cast<unsigned int>(index) > text.Length()) || (length < 0)) { throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); @@ -1649,7 +1685,7 @@ void LineParser::EvalLeft() m_valueStackPtr -= 1; String text = value1.GetString(); - int count = static_cast<int>(value2.GetNumber()); + int count = ConvertDoubleToInt(value2.GetNumber()); if ((count < 0) || (static_cast<unsigned int>(count) > text.Length())) { throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); @@ -1679,7 +1715,7 @@ void LineParser::EvalRight() m_valueStackPtr -= 1; String text = value1.GetString(); - int count = static_cast<int>(value2.GetNumber()); + int count = ConvertDoubleToInt(value2.GetNumber()); if ((count < 0) || (static_cast<unsigned int>(count) > text.Length())) { throw AsmException_SyntaxError_IllegalOperation( m_line, m_column ); @@ -1709,7 +1745,7 @@ void LineParser::EvalString() } m_valueStackPtr -= 1; - int count = static_cast<int>(value1.GetNumber()); + int count = ConvertDoubleToInt(value1.GetNumber()); String text = value2.GetString(); if ((count < 0) || (count >= 0x10000) || (text.Length() >= 0x10000) || (static_cast<unsigned int>(count) * text.Length() >= 0x10000)) { diff --git a/src/lineparser.h b/src/lineparser.h index ac6d9d2..294bd13 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -178,8 +178,10 @@ class LineParser std::pair<Value, Value> StackTopTwoValues(); String StackTopString(); double StackTopNumber(); + int StackTopInt(); std::pair<double, double> StackTopTwoNumbers(); std::pair<int, int> StackTopTwoInts(); + int ConvertDoubleToInt(double value); void EvalAdd(); void EvalSubtract(); diff --git a/test/2-expressions/intrangehi.fail.6502 b/test/2-expressions/intrangehi.fail.6502 new file mode 100644 index 0000000..051c40a --- /dev/null +++ b/test/2-expressions/intrangehi.fail.6502 @@ -0,0 +1,2 @@ +\ This is just out of range +print int(4294967296) diff --git a/test/2-expressions/intrangelo.fail.6502 b/test/2-expressions/intrangelo.fail.6502 new file mode 100644 index 0000000..65eef50 --- /dev/null +++ b/test/2-expressions/intrangelo.fail.6502 @@ -0,0 +1,2 @@ +\ This is just out of range +print int(-2147483649) diff --git a/test/2-expressions/operators.6502 b/test/2-expressions/operators.6502 index 972040a..908d34c 100644 --- a/test/2-expressions/operators.6502 +++ b/test/2-expressions/operators.6502 @@ -54,6 +54,12 @@ assert(sgn(0)=0) assert(sgn(-56)=-1) assert(sgn(56)=1) +\ Integer conversions +assert(int(&FFFFFFFF)=-1) +assert(int(4294967295)=-1) +assert(int(&80000000)=-2147483648) +assert(int(-2147483648)=-2147483648) + e=0.000000000001 assert(sin(0)=0) assert(sin(PI/2)=1) From efa6da479732dda2e951c6528000522979dca69f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 19 Feb 2022 23:46:47 +0000 Subject: [PATCH 126/144] Forgot to #include <climits> --- src/expression.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/expression.cpp b/src/expression.cpp index 0684bbb..3359b03 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -28,6 +28,7 @@ #include <cerrno> #include <sstream> #include <iomanip> +#include <climits> #include "lineparser.h" #include "asmexception.h" From b06d1ba7719af73212b64e99fce2ae97b9297ad8 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Tue, 22 Feb 2022 16:45:04 +0000 Subject: [PATCH 127/144] Fix ArgListParser to use ConvertDoubleToInt --- src/commands.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 681ee21..02c2e97 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -294,12 +294,12 @@ class ArgListParser IntArg ParseInt() { - return ParseNumber<IntArg>(); + return ParseNumber<IntArg>(&ArgListParser::ConvertDoubleToInt); } DoubleArg ParseDouble() { - return ParseNumber<DoubleArg>(); + return ParseNumber<DoubleArg>(&ArgListParser::ConvertDoubleToDouble); } StringArg ParseString() @@ -352,7 +352,17 @@ class ArgListParser ArgListParser(const ArgListParser& that); ArgListParser operator=(const ArgListParser& that); - template <class T> T ParseNumber() + int ConvertDoubleToInt(double value) + { + return m_lineParser.ConvertDoubleToInt(value); + } + + double ConvertDoubleToDouble(double value) + { + return value; + } + + template <class T> T ParseNumber(typename T::ContainedType (ArgListParser::*convertDoubleTo)(double)) { if ( !ReadPending() ) { @@ -368,7 +378,7 @@ class ArgListParser return T(m_lineParser.m_line, m_paramColumn, T::StateTypeMismatch); } m_pending = false; - return T(m_lineParser.m_line, m_paramColumn, static_cast<typename T::ContainedType>(m_pendingValue.GetNumber())); + return T(m_lineParser.m_line, m_paramColumn, (this->*convertDoubleTo)(m_pendingValue.GetNumber())); } // Return true if an argument is available From a829b150d32e57497f8bfdf53e1290c3b9ca545c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 18 Aug 2022 19:57:04 +0100 Subject: [PATCH 128/144] Underscores in numeric literals. Fixes #36, #48. --- src/VS2010/BeebAsm.vcxproj | 2 + src/VS2010/BeebAsm.vcxproj.filters | 6 + src/expression.cpp | 73 +------- src/literals.cpp | 246 +++++++++++++++++++++++++ src/literals.h | 25 +++ src/symboltable.cpp | 69 +------ test/1-values/underscorebin1.fail.6502 | 2 + test/1-values/underscorebin2.fail.6502 | 2 + test/1-values/underscorebin3.fail.6502 | 2 + test/1-values/underscoredec1.fail.6502 | 2 + test/1-values/underscoredec2.fail.6502 | 2 + test/1-values/underscoredec3.fail.6502 | 2 + test/1-values/underscorehex1.fail.6502 | 2 + test/1-values/underscorehex2.fail.6502 | 2 + test/1-values/underscorehex3.fail.6502 | 2 + test/1-values/values.6502 | 16 ++ test/2-expressions/issue36.6502 | 5 + 17 files changed, 332 insertions(+), 128 deletions(-) create mode 100644 src/literals.cpp create mode 100644 src/literals.h create mode 100644 test/1-values/underscorebin1.fail.6502 create mode 100644 test/1-values/underscorebin2.fail.6502 create mode 100644 test/1-values/underscorebin3.fail.6502 create mode 100644 test/1-values/underscoredec1.fail.6502 create mode 100644 test/1-values/underscoredec2.fail.6502 create mode 100644 test/1-values/underscoredec3.fail.6502 create mode 100644 test/1-values/underscorehex1.fail.6502 create mode 100644 test/1-values/underscorehex2.fail.6502 create mode 100644 test/1-values/underscorehex3.fail.6502 create mode 100644 test/2-expressions/issue36.6502 diff --git a/src/VS2010/BeebAsm.vcxproj b/src/VS2010/BeebAsm.vcxproj index 10e8197..60fab5e 100644 --- a/src/VS2010/BeebAsm.vcxproj +++ b/src/VS2010/BeebAsm.vcxproj @@ -86,6 +86,7 @@ <ClCompile Include="..\expression.cpp" /> <ClCompile Include="..\globaldata.cpp" /> <ClCompile Include="..\lineparser.cpp" /> + <ClCompile Include="..\literals.cpp" /> <ClCompile Include="..\macro.cpp" /> <ClCompile Include="..\main.cpp" /> <ClCompile Include="..\objectcode.cpp" /> @@ -102,6 +103,7 @@ <ClInclude Include="..\discimage.h" /> <ClInclude Include="..\globaldata.h" /> <ClInclude Include="..\lineparser.h" /> + <ClInclude Include="..\literals.h" /> <ClInclude Include="..\macro.h" /> <ClInclude Include="..\main.h" /> <ClInclude Include="..\objectcode.h" /> diff --git a/src/VS2010/BeebAsm.vcxproj.filters b/src/VS2010/BeebAsm.vcxproj.filters index cfd7bf3..47a7342 100644 --- a/src/VS2010/BeebAsm.vcxproj.filters +++ b/src/VS2010/BeebAsm.vcxproj.filters @@ -63,6 +63,9 @@ <ClCompile Include="..\random.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\literals.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\asmexception.h"> @@ -110,5 +113,8 @@ <ClInclude Include="..\value.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\literals.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/expression.cpp b/src/expression.cpp index 3359b03..d4de0ae 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -39,6 +39,7 @@ #include "random.h" #include "constants.h" #include "stringutils.h" +#include "literals.h" using namespace std; @@ -139,76 +140,10 @@ Value LineParser::GetValue() { Value value; - if ( m_column < m_line.length() && ( isdigit( m_line[ m_column ] ) || m_line[ m_column ] == '.' ) ) + double double_value; + if ( Literals::ParseNumeric(m_line, m_column, double_value) ) { - // get a number - - istringstream str( m_line ); - str.seekg( m_column ); - double number; - str >> number; - if (str.fail()) - { - // A decimal point with no number will cause this - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - value = number; - m_column = static_cast< size_t >( str.tellg() ); - } - else if ( m_column < m_line.length() && ( m_line[ m_column ] == '&' || m_line[ m_column ] == '$' ) ) - { - // get hexadecimal - - // skip the number prefix - m_column++; - - unsigned int hexValue; - - istringstream str( m_line ); - str.seekg( m_column ); - if (str >> hex >> hexValue) - { - m_column = static_cast< size_t >( str.tellg() ); - - value = static_cast< double >( hexValue ); - } - else - { - throw AsmException_SyntaxError_BadHex( m_line, m_column ); - } - } - else if ( m_column < m_line.length() && m_line[ m_column ] == '%' ) - { - // get binary - - // skip the number prefix - m_column++; - - size_t start_column = m_column; - unsigned int binValue = 0; - - // Skip leading zeroes - while ( m_column < m_line.length() && m_line[ m_column ] == '0' ) - { - m_column++; - } - - // Remember the column containing the first one (if any) - size_t first_one = m_column; - - while ( m_column < m_line.length() && ( m_line[ m_column ] == '0' || m_line[ m_column ] == '1' ) ) - { - binValue = ( binValue * 2 ) + ( m_line[ m_column ] - '0' ); - m_column++; - } - - if ( m_column == start_column || m_column - first_one > 32 ) - { - // badly formed bin literal - throw AsmException_SyntaxError_BadBin( m_line, m_column ); - } - - value = static_cast< double >( binValue ); + value = double_value; } else if ( m_column < m_line.length() && m_line[ m_column ] == '*' ) { diff --git a/src/literals.cpp b/src/literals.cpp new file mode 100644 index 0000000..33af3e0 --- /dev/null +++ b/src/literals.cpp @@ -0,0 +1,246 @@ +/*************************************************************************************************/ +/** + literals.cpp + + + Copyright (C) Rich Talbot-Watkins, Charles Reilly 2007-2022 + + This file is part of BeebAsm. + + BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU + General Public License as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with BeebAsm, as + COPYING.txt. If not, see <http://www.gnu.org/licenses/>. +*/ +/*************************************************************************************************/ + +#include <cassert> + +#include "asmexception.h" +#include "literals.h" + +// Don't use isdigit because compilers can't agree on its locale dependence +static bool is_decimal_digit(char c) +{ + return '0' <= c && c <= '9'; +} + +static int hex_digit_value(char c) +{ + if ( '0' <= c && c <= '9' ) + { + return c - '0'; + } + else if ( 'A' <= c && c <= 'F' ) + { + return c - 'A' + 10; + } + else if ( 'a' <= c && c <= 'f' ) + { + return c - 'a' + 10; + } + else + { + return -1; + } +} + +// Parse a binary or hex integer, return false if it is malformed or too big +static bool ParseInteger(const std::string& line, size_t& index, int base, int max_digits, double& result) +{ + size_t start_column = index; + + // Check there's something and it doesn't start with an underscore + if (index == line.length() || line[index] == '_') + { + return false; + } + + // Skip leading zeroes + while ( index < line.length() && (line[index] == '0' || line[index] == '_') ) + { + index++; + } + + unsigned int value = 0; + int digit_count = 0; + + while ( index < line.length() ) + { + if (line[index] == '_') + { + // Don't allow two in a row; there must be a previous char because + // we can't start with an underscore + if (line[index - 1] == '_') + { + return false; + } + } + else + { + int digit = hex_digit_value(line[index]); + if ( digit < 0 || digit >= base ) + { + break; + } + value = ( value * base ) + digit; + digit_count++; + } + index++; + } + + // Check we found something, it wasn't too long and it didn't end on an underscore + if ( index == start_column || digit_count > max_digits || line[index - 1] == '_') + { + return false; + } + + result = static_cast< double >( value ); + + return true; +} + +// Copy decimal digits to a buffer, skipping single underscores that appear between digits. +// Throw an exception for underscores at the beginning or end or paired. +// Return false if there were no digits. +static bool CopyDigitsSkippingUnderscores(const std::string& line, size_t& index, std::string& buffer) +{ + if ( index < line.length() && line[index] == '_') + { + // Number can't start with an underscore + throw AsmException_SyntaxError_InvalidCharacter( line, index ); + } + + size_t start_index = index; + while ( index < line.length() && (is_decimal_digit(line[index]) || line[index] == '_') ) + { + if (line[index] == '_') + { + // Don't allow two in a row; there must be a previous char because + // we can't start with an underscore + if (line[index - 1] == '_') + { + throw AsmException_SyntaxError_InvalidCharacter( line, index ); + } + } + else + { + buffer.push_back(line[index]); + } + index++; + } + if ( index > start_index && line[index - 1] == '_') + { + // Can't end on an underscore + throw AsmException_SyntaxError_InvalidCharacter( line, index ); + } + return index != start_index; +} + +/*************************************************************************************************/ +/** + Literals::ParseNumeric() + + Parses a simple numeric value. This may be + - a decimal literal + - a hex literal (prefixed by & or $) + - a binary literal (prefixed by %) + + Any two digits may be separated by a single underscore, which will be ignored. + + Returns false if there isn't a numeric literal, throws an exception if it is malformed +*/ +/*************************************************************************************************/ +bool Literals::ParseNumeric(const std::string& line, size_t& index, double& result) +{ + if ( index >= line.length() ) + { + return false; + } + + if ( is_decimal_digit(line[index]) || line[index] == '.' ) + { + // Copy the number without underscores to this buffer + std::string buffer; + + // Copy digits preceding decimal point + bool have_digits = CopyDigitsSkippingUnderscores(line, index, buffer); + + // Copy decimal point + if ( index < line.length() && line[index] == '.') + { + buffer.push_back(line[index]); + index++; + + // Copy digits after decimal point + have_digits = CopyDigitsSkippingUnderscores(line, index, buffer) || have_digits; + } + + if ( !have_digits ) + { + // A decimal point with no number will cause this + throw AsmException_SyntaxError_InvalidCharacter( line, index ); + } + + // Copy exponent + if ( index < line.length() && ( line[index] == 'e' || line[index] == 'E' ) ) + { + buffer.push_back(line[index]); + index++; + if ( index < line.length() && ( line[index] == '+' || line[index] == '-' ) ) + { + buffer.push_back(line[index]); + index++; + } + if (!CopyDigitsSkippingUnderscores(line, index, buffer)) + { + // Exponent needs a value + throw AsmException_SyntaxError_InvalidCharacter( line, index ); + } + } + + char* end_ptr; + result = strtod(buffer.c_str(), &end_ptr); + assert(*end_ptr == 0); + + return true; + } + else if ( (line[index] == '&') || (line[index] == '$') ) + { + // get hexadecimal + + // skip the number prefix + index++; + + if (!ParseInteger(line, index, 16, 8, result)) + { + // badly formed hex literal + throw AsmException_SyntaxError_BadHex( line, index ); + } + + return true; + } + else if ( line[index] == '%' ) + { + // get binary + + // skip the number prefix + index++; + + if (!ParseInteger(line, index, 2, 32, result)) + { + // badly formed bin literal + throw AsmException_SyntaxError_BadBin( line, index ); + } + + return true; + } + + return false; +} diff --git a/src/literals.h b/src/literals.h new file mode 100644 index 0000000..592fb14 --- /dev/null +++ b/src/literals.h @@ -0,0 +1,25 @@ +/*************************************************************************************************/ +/** + literals.h + + + Copyright (C) Rich Talbot-Watkins, Charles Reilly 2007-2022 + + This file is part of BeebAsm. + + BeebAsm is free software: you can redistribute it and/or modify it under the terms of the GNU + General Public License as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + BeebAsm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with BeebAsm, as + COPYING.txt. If not, see <http://www.gnu.org/licenses/>. +*/ +/*************************************************************************************************/ + +namespace Literals { + bool ParseNumeric(const std::string& line, size_t& index, double& result); +} diff --git a/src/symboltable.cpp b/src/symboltable.cpp index b4ea003..63eb7d8 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -30,6 +30,8 @@ #include "objectcode.h" #include "symboltable.h" #include "constants.h" +#include "asmexception.h" +#include "literals.h" using namespace std; @@ -182,76 +184,25 @@ bool SymbolTable::AddCommandLineSymbol( const std::string& expr ) return false; } - bool readHex = false; - bool readBinary = false; - - if ( !valueString.compare( 0, 1, "&" ) || !valueString.compare( 0, 1, "$" ) ) - { - readHex = true; - valueString = valueString.substr( 1 ); - } - else if ( !valueString.compare( 0, 2, "0x" ) || !valueString.compare( 0, 2, "0X" ) ) + // Convert C-style hex prefix to beeb-style + if ( !valueString.compare( 0, 2, "0x" ) || !valueString.compare( 0, 2, "0X" ) ) { - readHex = true; - valueString = valueString.substr( 2 ); - } - else if ( !valueString.compare( 0, 1, "%" ) ) - { - readBinary = true; valueString = valueString.substr( 1 ); + valueString[0] = '&'; } - std::istringstream valueStream( valueString ); + size_t index = 0; double value; - char c; - - valueStream >> noskipws; - - if ( readHex ) - { - int intValue; - - if ( ! ( valueStream >> hex >> intValue ) ) - { - return false; - } - - value = intValue; - } - else if ( readBinary ) + try { - unsigned int intValue = 0; - - int charOrEof = valueStream.get(); - - if ( charOrEof == EOF ) - { - return false; - } - - while ( ( charOrEof == '0' ) || ( charOrEof == '1' ) ) - { - if ( intValue & 0x80000000 ) - { - return false; - } - intValue = 2 * intValue + (charOrEof - '0'); - charOrEof = valueStream.get(); - } - - if ( charOrEof != EOF ) - { - return false; - } - - value = static_cast<double>(intValue); + Literals::ParseNumeric(valueString, index, value); } - else if ( ! ( valueStream >> value ) ) + catch (AsmException_SyntaxError const&) { return false; } - if ( valueStream.get( c ) ) + if (index != valueString.length()) { return false; } diff --git a/test/1-values/underscorebin1.fail.6502 b/test/1-values/underscorebin1.fail.6502 new file mode 100644 index 0000000..688047f --- /dev/null +++ b/test/1-values/underscorebin1.fail.6502 @@ -0,0 +1,2 @@ +\ Binary literal with leading underscore +A=%_1 diff --git a/test/1-values/underscorebin2.fail.6502 b/test/1-values/underscorebin2.fail.6502 new file mode 100644 index 0000000..2c2a33a --- /dev/null +++ b/test/1-values/underscorebin2.fail.6502 @@ -0,0 +1,2 @@ +\ Binary literal with paired underscores +A=%1__1 diff --git a/test/1-values/underscorebin3.fail.6502 b/test/1-values/underscorebin3.fail.6502 new file mode 100644 index 0000000..ec6b19d --- /dev/null +++ b/test/1-values/underscorebin3.fail.6502 @@ -0,0 +1,2 @@ +\ Binary literal with trailing underscore +A=%1_ diff --git a/test/1-values/underscoredec1.fail.6502 b/test/1-values/underscoredec1.fail.6502 new file mode 100644 index 0000000..1296f51 --- /dev/null +++ b/test/1-values/underscoredec1.fail.6502 @@ -0,0 +1,2 @@ +\ Decimal literal with leading underscore +A=1._1 diff --git a/test/1-values/underscoredec2.fail.6502 b/test/1-values/underscoredec2.fail.6502 new file mode 100644 index 0000000..1840c73 --- /dev/null +++ b/test/1-values/underscoredec2.fail.6502 @@ -0,0 +1,2 @@ +\ Decimal literal with paired underscores +A=1__1 diff --git a/test/1-values/underscoredec3.fail.6502 b/test/1-values/underscoredec3.fail.6502 new file mode 100644 index 0000000..f34c70f --- /dev/null +++ b/test/1-values/underscoredec3.fail.6502 @@ -0,0 +1,2 @@ +\ Decimal literal with trailing underscore +A=1_ diff --git a/test/1-values/underscorehex1.fail.6502 b/test/1-values/underscorehex1.fail.6502 new file mode 100644 index 0000000..0141b2a --- /dev/null +++ b/test/1-values/underscorehex1.fail.6502 @@ -0,0 +1,2 @@ +\ Hex literal with leading underscore +A=&_1 diff --git a/test/1-values/underscorehex2.fail.6502 b/test/1-values/underscorehex2.fail.6502 new file mode 100644 index 0000000..9407dcf --- /dev/null +++ b/test/1-values/underscorehex2.fail.6502 @@ -0,0 +1,2 @@ +\ Hex literal with paired underscores +A=&a__b diff --git a/test/1-values/underscorehex3.fail.6502 b/test/1-values/underscorehex3.fail.6502 new file mode 100644 index 0000000..8f8bff3 --- /dev/null +++ b/test/1-values/underscorehex3.fail.6502 @@ -0,0 +1,2 @@ +\ Hex literal with trailing underscore +A=&1_ diff --git a/test/1-values/values.6502 b/test/1-values/values.6502 index 0e2ee0c..7cb8a5a 100644 --- a/test/1-values/values.6502 +++ b/test/1-values/values.6502 @@ -1,6 +1,10 @@ assert(&10=16) +assert(&2_3=35) assert($10=16) +assert($2_3=35) +assert(1_234=1234) assert(%10000=16) +assert(%1_0000=16) assert('A'=&41) assert(&13579BDF=324508639) assert(&2468ACE0=610839776) @@ -13,3 +17,15 @@ assert(%000011111111111111111111111111111111=&ffffffff) \ Program counter assert(*=0) +assert(1.=1.0) +assert(.1=0.1) +assert(12e2=1200) +assert(12.e2=1200) +assert(12.3e2=1230) +assert(12.34e2=1234) +assert(1_2e2=1200) +assert(1_2.e2=1200) +assert(1_2.3e2=1230) +assert(1_2.3_4e2=1234) +assert(12e+2=1200) +assert(12e-2=0.12) diff --git a/test/2-expressions/issue36.6502 b/test/2-expressions/issue36.6502 new file mode 100644 index 0000000..5fbb8d1 --- /dev/null +++ b/test/2-expressions/issue36.6502 @@ -0,0 +1,5 @@ +\ This could fail with libc++ because operator>> consumes 128A +\ rather than just 128. Fixed by parsing numbers ourselves. +\ https://github.com/stardot/beebasm/issues/36 +org 0:Q%=255:equb (128ANDQ%) +ASSERT((252AND63)=60) From 1bcf83d86d26751faeed27d062d349d8d81392f7 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 19 Aug 2022 09:45:14 +0100 Subject: [PATCH 129/144] Underscores in numeric literals - update README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4221b88..6f4c702 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Instructions can be written one-per-line, or many on one line, separated by colo Comments are introduced by a semicolon or backslash. Unlike the BBC Micro assembler, these continue to the end of the line, and are not terminated by a colon (because this BBC Micro feature is horrible!). -Numeric literals are in decimal by default, and can be integers or reals. Hex literals are prefixed with `"&"` or `"$"`, binary literals are prefixed with `"%"`. A character in single quotes (e.g. `'A'`) returns its ASCII code. +Numeric literals are in decimal by default, and can be integers or reals. Scientific notation can be used, e.g. `1.2E10` or `1e-7`. Hex literals are prefixed with `"&"` or `"$"`, binary literals are prefixed with `"%"`. Digits can be separated with an underscore to improve readability, e.g. `1_000_000` or `$1_0000`. A character in single quotes (e.g. `'A'`) returns its ASCII code. BeebAsm can accept complex expressions, using a wide variety of operators and functions. Here's a summary: @@ -743,6 +743,9 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, LEFT$, RIGHT$, STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with thanks to Steven Flintham.) + Support underscore separators in numeric literals. C++'s built-in + parsing was used previously so beebasm's numeric syntax varied + between compilers and platforms; this is fixed. 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From d325f2b96118ea0529231e5f4e4bd480dccd1a7c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 19 Aug 2022 10:50:05 +0100 Subject: [PATCH 130/144] Don't mistake an EOR for an exponent in e.g. 2EOR3 --- src/literals.cpp | 10 ++++++---- test/2-expressions/notexponent.6502 | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 test/2-expressions/notexponent.6502 diff --git a/src/literals.cpp b/src/literals.cpp index 33af3e0..7a24c88 100644 --- a/src/literals.cpp +++ b/src/literals.cpp @@ -188,12 +188,14 @@ bool Literals::ParseNumeric(const std::string& line, size_t& index, double& resu throw AsmException_SyntaxError_InvalidCharacter( line, index ); } - // Copy exponent - if ( index < line.length() && ( line[index] == 'e' || line[index] == 'E' ) ) + // Copy exponent if it's followed by a sign or digit + if ( index + 1 < line.length() && + ( line[index] == 'e' || line[index] == 'E' ) && + ( line[index + 1] == '+' || line[index + 1] == '-' || is_decimal_digit(line[index + 1]) ) ) { - buffer.push_back(line[index]); + buffer.push_back('e'); index++; - if ( index < line.length() && ( line[index] == '+' || line[index] == '-' ) ) + if ( line[index] == '+' || line[index] == '-' ) { buffer.push_back(line[index]); index++; diff --git a/test/2-expressions/notexponent.6502 b/test/2-expressions/notexponent.6502 new file mode 100644 index 0000000..0e0bb2e --- /dev/null +++ b/test/2-expressions/notexponent.6502 @@ -0,0 +1,2 @@ +\ Don't mistake an EOR for an exponent +ASSERT((252EOR63)=195) From 90f393c9952898296533f311c9533ef55327818f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Fri, 19 Aug 2022 13:12:33 +0100 Subject: [PATCH 131/144] Underscores in numeric literals - beebasm -D tests --- test/1-values/cmdlinedefinebin.6502 | 3 +++ test/1-values/cmdlinedefinedec.6502 | 3 +++ test/1-values/cmdlinedefinedec.fail.6502 | 2 ++ test/1-values/cmdlinedefinehex1.6502 | 3 +++ test/1-values/cmdlinedefinehex2.6502 | 3 +++ test/1-values/cmdlinedefinehex3.6502 | 3 +++ test/1-values/cmdlinedefinehex4.6502 | 3 +++ 7 files changed, 20 insertions(+) create mode 100644 test/1-values/cmdlinedefinebin.6502 create mode 100644 test/1-values/cmdlinedefinedec.6502 create mode 100644 test/1-values/cmdlinedefinedec.fail.6502 create mode 100644 test/1-values/cmdlinedefinehex1.6502 create mode 100644 test/1-values/cmdlinedefinehex2.6502 create mode 100644 test/1-values/cmdlinedefinehex3.6502 create mode 100644 test/1-values/cmdlinedefinehex4.6502 diff --git a/test/1-values/cmdlinedefinebin.6502 b/test/1-values/cmdlinedefinebin.6502 new file mode 100644 index 0000000..03d9830 --- /dev/null +++ b/test/1-values/cmdlinedefinebin.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=%1_1011 +\ Check parsing of values defined on the command-line with -D +assert(V=27) diff --git a/test/1-values/cmdlinedefinedec.6502 b/test/1-values/cmdlinedefinedec.6502 new file mode 100644 index 0000000..0e95aaa --- /dev/null +++ b/test/1-values/cmdlinedefinedec.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=1_2.3_4 +\ Check parsing of values defined on the command-line with -D +assert(V=12.34) diff --git a/test/1-values/cmdlinedefinedec.fail.6502 b/test/1-values/cmdlinedefinedec.fail.6502 new file mode 100644 index 0000000..1cc6ca3 --- /dev/null +++ b/test/1-values/cmdlinedefinedec.fail.6502 @@ -0,0 +1,2 @@ +\ beebasm -D V=1_2.3_ +\ Check parsing of values defined on the command-line with -D diff --git a/test/1-values/cmdlinedefinehex1.6502 b/test/1-values/cmdlinedefinehex1.6502 new file mode 100644 index 0000000..fb2df01 --- /dev/null +++ b/test/1-values/cmdlinedefinehex1.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=&1_b +\ Check parsing of values defined on the command-line with -D +assert(V=27) diff --git a/test/1-values/cmdlinedefinehex2.6502 b/test/1-values/cmdlinedefinehex2.6502 new file mode 100644 index 0000000..beae357 --- /dev/null +++ b/test/1-values/cmdlinedefinehex2.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=$1_b +\ Check parsing of values defined on the command-line with -D +assert(V=27) diff --git a/test/1-values/cmdlinedefinehex3.6502 b/test/1-values/cmdlinedefinehex3.6502 new file mode 100644 index 0000000..a6a0e73 --- /dev/null +++ b/test/1-values/cmdlinedefinehex3.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=0x1_b +\ Check parsing of values defined on the command-line with -D +assert(V=27) diff --git a/test/1-values/cmdlinedefinehex4.6502 b/test/1-values/cmdlinedefinehex4.6502 new file mode 100644 index 0000000..071c4d1 --- /dev/null +++ b/test/1-values/cmdlinedefinehex4.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=0X1_b +\ Check parsing of values defined on the command-line with -D +assert(V=27) From 60e697e2e1dd50864285eee8aa0058f73512c768 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Thu, 25 Aug 2022 19:18:13 +0100 Subject: [PATCH 132/144] First pass of COPYBLOCK marks dest as USED. Fixes #75. --- src/commands.cpp | 2 +- src/objectcode.cpp | 16 ++++++++++++++-- src/objectcode.h | 7 ++++++- test/3-directives/copyblock/copyblockreuse.6502 | 8 ++------ test/3-directives/copyblock/issue75.6502 | 10 ++++++++++ 5 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 test/3-directives/copyblock/issue75.6502 diff --git a/src/commands.cpp b/src/commands.cpp index be164d6..c53a8a2 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1801,7 +1801,7 @@ void LineParser::HandleCopyBlock() try { - ObjectCode::Instance().CopyBlock( start, end, dest ); + ObjectCode::Instance().CopyBlock( start, end, dest, GlobalData::Instance().IsFirstPass() ); } catch ( AsmException_AssembleError& e ) { diff --git a/src/objectcode.cpp b/src/objectcode.cpp index 0a9beac..fb42528 100644 --- a/src/objectcode.cpp +++ b/src/objectcode.cpp @@ -440,7 +440,7 @@ int ObjectCode::GetMapping( int ascii ) const ObjectCode::CopyBlock() */ /*************************************************************************************************/ -void ObjectCode::CopyBlock( int start, int end, int dest ) +void ObjectCode::CopyBlock( int start, int end, int dest, bool firstPass ) { int length = end - start; @@ -450,7 +450,19 @@ void ObjectCode::CopyBlock( int start, int end, int dest ) throw AsmException_AssembleError_OutOfMemory(); } - if ( start < dest ) + if (firstPass) + { + for ( int i = 0; i < length; i++ ) + { + if ( m_aFlags[ dest + i ] & GUARD ) + { + throw AsmException_AssembleError_GuardHit(); + } + + m_aFlags[ dest + i ] |= (m_aFlags[ start + i ] & USED); + } + } + else if ( start < dest ) { for ( int i = length - 1; i >= 0; i-- ) { diff --git a/src/objectcode.h b/src/objectcode.h index 3220f1c..fde8370 100644 --- a/src/objectcode.h +++ b/src/objectcode.h @@ -57,15 +57,20 @@ class ObjectCode void SetMapping( int ascii, int mapped ); int GetMapping( int ascii ) const; - void CopyBlock( int start, int end, int dest ); + void CopyBlock( int start, int end, int dest, bool firstPass ); private: + // Each byte in the memory map has a set of flags enum FLAGS { + // This memory location has been used so don't assemble over it USED = (1 << 0), + // This memory location has been guarded so don't assemble over it GUARD = (1 << 1), + // On the second pass, check that opcodes match what was written on the first pass CHECK = (1 << 2), + // Suppress the opcode check (set by CLEAR) DONT_CHECK = (1 << 3) }; diff --git a/test/3-directives/copyblock/copyblockreuse.6502 b/test/3-directives/copyblock/copyblockreuse.6502 index ad4af51..4bff157 100644 --- a/test/3-directives/copyblock/copyblockreuse.6502 +++ b/test/3-directives/copyblock/copyblockreuse.6502 @@ -1,14 +1,9 @@ -\ COPYBLOCK - reuse memory from source of copy +\ COPYBLOCK - reuse memory from source of copy using CLEAR ORG &2000 .start -\ This CLEAR is required otherwise the second pass grumbles here -\ about the LDX #&00 later in the first pass. This is rather -\ unintuitive. -CLEAR start, start+2 - LDY #&00 .loop LDA (&70),Y @@ -19,6 +14,7 @@ BNE loop SKIP loopend-start COPYBLOCK start,loopend,loopend +CLEAR start, start+2 .end diff --git a/test/3-directives/copyblock/issue75.6502 b/test/3-directives/copyblock/issue75.6502 new file mode 100644 index 0000000..7082440 --- /dev/null +++ b/test/3-directives/copyblock/issue75.6502 @@ -0,0 +1,10 @@ +\ Don't copy memory on first pass of COPYBLOCK. +\ Commit 54bbcd232bb82b77a6758ecf3b02b4338a7f5c2c broke this in v1.10rc1. + +ORG &1200 +LDA #0 + +ORG &7900 +NOP + +COPYBLOCK &7900, &7902, &1200 From 5d1e985b18fd58c077b0bfc3a5632e833e63428c Mon Sep 17 00:00:00 2001 From: Steven Flintham <sgf@lemma.co.uk> Date: Fri, 26 Aug 2022 02:16:10 +0100 Subject: [PATCH 133/144] Change -writes to -cycle (issue #74) --- README.md | 6 +++--- src/discimage.cpp | 2 +- src/globaldata.cpp | 2 +- src/globaldata.h | 6 +++--- src/main.cpp | 12 ++++++------ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f0c5012..6783705 100644 --- a/README.md +++ b/README.md @@ -135,9 +135,9 @@ If specified, this sets the disc option of the generated disc image (i.e. the `* If specified, this sets the disc title of the generated disc image (i.e. the string set by `*TITLE`) to the value specified. -`-writes <n>` +`-cycle <n>` -If specified, this sets the number of writes for the generated disc image (i.e. the number shown next to the title in the disc catalogue) to the value specified. +If specified, this sets the cycle for the generated disc image (i.e. the number shown next to the title in the disc catalogue) to the value specified. `-di <filename>` @@ -743,7 +743,7 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) - Added -writes, -dd and -labels options (thanks to tricky for these) + Added -cycle, -dd and -labels options (thanks to tricky for these) Added CMake support and man page (thanks to Dave Lambley) Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, LEFT$, RIGHT$, STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with diff --git a/src/discimage.cpp b/src/discimage.cpp index 87e3393..7fff2e4 100644 --- a/src/discimage.cpp +++ b/src/discimage.cpp @@ -112,7 +112,7 @@ DiscImage::DiscImage( const char* pOutput, const char* pInput ) // generate a blank catalog memset( m_aCatalog, 0, 0x200 ); - m_aCatalog[ 0x104 ] = GlobalData::Instance().GetDiscWrites(); + m_aCatalog[ 0x104 ] = GlobalData::Instance().GetDiscCycle(); m_aCatalog[ 0x106 ] = 0x03 | ( ( GlobalData::Instance().GetDiscOption() & 3 ) << 4); m_aCatalog[ 0x107 ] = 0x20; diff --git a/src/globaldata.cpp b/src/globaldata.cpp index afe3040..2de7f17 100644 --- a/src/globaldata.cpp +++ b/src/globaldata.cpp @@ -77,7 +77,7 @@ GlobalData::GlobalData() m_pOutputFile( NULL ), m_numAnonSaves( 0 ), m_discOption( 0 ), - m_discWrites( 0 ), + m_discCycle( 0 ), m_assemblyTime( time( NULL ) ), m_bRequireDistinctOpcodes( false ), m_bUseVisualCppErrorFormat( false ) diff --git a/src/globaldata.h b/src/globaldata.h index 5678119..8d439c9 100644 --- a/src/globaldata.h +++ b/src/globaldata.h @@ -49,7 +49,7 @@ class GlobalData inline void SetOutputFile( const char* p ) { m_pOutputFile = p; } inline void IncNumAnonSaves() { m_numAnonSaves++; } inline void SetDiscOption( int opt ) { m_discOption = opt; } - inline void SetDiscWrites( int num ) { m_discWrites = num; } + inline void SetDiscCycle( int num ) { m_discCycle = num; } inline void SetDiscTitle( const std::string& t ) { m_discTitle = t; } inline void SetRequireDistinctOpcodes ( bool b ) @@ -70,7 +70,7 @@ class GlobalData inline const char* GetOutputFile() const { return m_pOutputFile; } inline int GetNumAnonSaves() const { return m_numAnonSaves; } inline int GetDiscOption() const { return m_discOption; } - inline int GetDiscWrites() const { return m_discWrites; } + inline int GetDiscCycle() const { return m_discCycle; } inline const std::string& GetDiscTitle() const { return m_discTitle; } inline time_t GetAssemblyTime() const { return m_assemblyTime; } @@ -95,7 +95,7 @@ class GlobalData const char* m_pOutputFile; int m_numAnonSaves; int m_discOption; - int m_discWrites; + int m_discCycle; std::string m_discTitle; time_t m_assemblyTime; bool m_bRequireDistinctOpcodes; diff --git a/src/main.cpp b/src/main.cpp index 8e714be..44d1c8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,7 +74,7 @@ int main( int argc, char* argv[] ) WAITING_FOR_BOOT_FILENAME, WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, - WAITING_FOR_DISC_WRITES, + WAITING_FOR_DISC_CYCLE, WAITING_FOR_SYMBOL, WAITING_FOR_STRING_SYMBOL, WAITING_FOR_LABELS_FILE @@ -127,9 +127,9 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_DISC_TITLE; } - else if ( strcmp( argv[i], "-writes" ) == 0 ) + else if ( strcmp( argv[i], "-cycle" ) == 0 ) { - state = WAITING_FOR_DISC_WRITES; + state = WAITING_FOR_DISC_CYCLE; } else if ( strcmp( argv[i], "-w" ) == 0 ) { @@ -177,7 +177,7 @@ int main( int argc, char* argv[] ) cout << " -labels <file> Specify a filename to export any labels dumped with -d or -dd to" << endl; cout << " -opt <opt> Specify the *OPT 4,n for the generated disc image" << endl; cout << " -title <title> Specify the title for the generated disc image" << endl; - cout << " -writes <n> Specify the number of writes for the generated disc image" << endl; + cout << " -cycle <n> Specify the cycle for the generated disc image" << endl; cout << " -v Verbose output" << endl; cout << " -d Dump all global symbols after assembly" << endl; cout << " -dd Dump all global and local symbols after assembly" << endl; @@ -250,9 +250,9 @@ int main( int argc, char* argv[] ) state = READY; break; - case WAITING_FOR_DISC_WRITES: + case WAITING_FOR_DISC_CYCLE: - GlobalData::Instance().SetDiscWrites( std::strtol( argv[i], NULL, 10 ) ); + GlobalData::Instance().SetDiscCycle( std::strtol( argv[i], NULL, 10 ) ); state = READY; break; From 8be43ef41a1f66ed50a75e871ef01d353cefae7f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 27 Aug 2022 13:32:30 +0100 Subject: [PATCH 134/144] Suppress unsaved warning if no memory used. Fixes #78. Also update version to v1.10rc2 --- src/main.cpp | 4 ++-- src/objectcode.cpp | 20 ++++++++++++++++++++ src/objectcode.h | 2 ++ test/3-directives/save/anyused.6502 | 3 +++ test/3-directives/save/anyused.gold.txt | 1 + 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/3-directives/save/anyused.6502 create mode 100644 test/3-directives/save/anyused.gold.txt diff --git a/src/main.cpp b/src/main.cpp index 44d1c8c..17fd68a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,7 +42,7 @@ using namespace std; -#define VERSION "1.10-pre" +#define VERSION "1.10rc2" /*************************************************************************************************/ @@ -350,7 +350,7 @@ int main( int argc, char* argv[] ) SymbolTable::Instance().Dump(bDumpSymbols, bDumpAllSymbols, pLabelsOutputFile); } - if ( !GlobalData::Instance().IsSaved() && exitCode == EXIT_SUCCESS ) + if ( !GlobalData::Instance().IsSaved() && ObjectCode::Instance().AnyUsed() && exitCode == EXIT_SUCCESS ) { cerr << "warning: no SAVE command in source file." << endl; } diff --git a/src/objectcode.cpp b/src/objectcode.cpp index fb42528..dfcdf78 100644 --- a/src/objectcode.cpp +++ b/src/objectcode.cpp @@ -491,3 +491,23 @@ void ObjectCode::CopyBlock( int start, int end, int dest, bool firstPass ) } } } + +#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a[0])) + +/*************************************************************************************************/ +/** + ObjectCode::AnyUsed() - is any memory USED? +*/ +/*************************************************************************************************/ +bool ObjectCode::AnyUsed() const +{ + for (int i = 0; i < ARRAY_LENGTH(m_aFlags); ++i) + { + if ( m_aFlags[i] & USED ) + { + return true; + } + } + + return false; +} diff --git a/src/objectcode.h b/src/objectcode.h index fde8370..db6414b 100644 --- a/src/objectcode.h +++ b/src/objectcode.h @@ -59,6 +59,8 @@ class ObjectCode void CopyBlock( int start, int end, int dest, bool firstPass ); + bool AnyUsed() const; + private: // Each byte in the memory map has a set of flags diff --git a/test/3-directives/save/anyused.6502 b/test/3-directives/save/anyused.6502 new file mode 100644 index 0000000..ce4f365 --- /dev/null +++ b/test/3-directives/save/anyused.6502 @@ -0,0 +1,3 @@ +\ Ensure "no save" warning checks up to the end of memory +ORG &FFFF +BRK diff --git a/test/3-directives/save/anyused.gold.txt b/test/3-directives/save/anyused.gold.txt new file mode 100644 index 0000000..5738da9 --- /dev/null +++ b/test/3-directives/save/anyused.gold.txt @@ -0,0 +1 @@ +warning: no SAVE command in source file. From d51b8d9690b661af2c2d64ca266cc4f93c7d143f Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 27 Aug 2022 13:48:15 +0100 Subject: [PATCH 135/144] Issue 78 - signed/unsigned comparison caused test failure --- src/objectcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objectcode.cpp b/src/objectcode.cpp index dfcdf78..478e545 100644 --- a/src/objectcode.cpp +++ b/src/objectcode.cpp @@ -501,7 +501,7 @@ void ObjectCode::CopyBlock( int start, int end, int dest, bool firstPass ) /*************************************************************************************************/ bool ObjectCode::AnyUsed() const { - for (int i = 0; i < ARRAY_LENGTH(m_aFlags); ++i) + for (unsigned int i = 0; i < ARRAY_LENGTH(m_aFlags); ++i) { if ( m_aFlags[i] & USED ) { From 99be22c0547b7e4fa081c7d4136690874347fe9c Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 16:11:53 +0100 Subject: [PATCH 136/144] Default and negative -D values broken. Fixes #83. --- src/literals.cpp | 8 +++++++- test/1-values/cmdlinedefinedefault.6502 | 3 +++ test/1-values/cmdlinedefinenegative.6502 | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/1-values/cmdlinedefinedefault.6502 create mode 100644 test/1-values/cmdlinedefinenegative.6502 diff --git a/src/literals.cpp b/src/literals.cpp index 7a24c88..1aae858 100644 --- a/src/literals.cpp +++ b/src/literals.cpp @@ -164,11 +164,17 @@ bool Literals::ParseNumeric(const std::string& line, size_t& index, double& resu return false; } - if ( is_decimal_digit(line[index]) || line[index] == '.' ) + if ( is_decimal_digit(line[index]) || line[index] == '.' || line[index] == '-' ) { // Copy the number without underscores to this buffer std::string buffer; + if ( line[index] == '-' ) + { + buffer.push_back('-'); + index++; + } + // Copy digits preceding decimal point bool have_digits = CopyDigitsSkippingUnderscores(line, index, buffer); diff --git a/test/1-values/cmdlinedefinedefault.6502 b/test/1-values/cmdlinedefinedefault.6502 new file mode 100644 index 0000000..d4edf01 --- /dev/null +++ b/test/1-values/cmdlinedefinedefault.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V +\ Check default command-line -D value is -1 +assert(V=-1) diff --git a/test/1-values/cmdlinedefinenegative.6502 b/test/1-values/cmdlinedefinenegative.6502 new file mode 100644 index 0000000..1f879c7 --- /dev/null +++ b/test/1-values/cmdlinedefinenegative.6502 @@ -0,0 +1,3 @@ +\ beebasm -D V=-1_2.3_4 +\ Check parsing of negative values defined on the command-line with -D +assert(V=-12.34) From 9cb5e158e6f586f9fe9c3730dc96057b8c60f4f2 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 18:35:31 +0100 Subject: [PATCH 137/144] Report missing -D value on command-line --- src/symboltable.cpp | 5 ++++- test/1-values/cmdlinedefinemissing.fail.6502 | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/1-values/cmdlinedefinemissing.fail.6502 diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 63eb7d8..d7d903b 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -195,7 +195,10 @@ bool SymbolTable::AddCommandLineSymbol( const std::string& expr ) double value; try { - Literals::ParseNumeric(valueString, index, value); + if ( !Literals::ParseNumeric(valueString, index, value) ) + { + return false; + } } catch (AsmException_SyntaxError const&) { diff --git a/test/1-values/cmdlinedefinemissing.fail.6502 b/test/1-values/cmdlinedefinemissing.fail.6502 new file mode 100644 index 0000000..aa2b4bf --- /dev/null +++ b/test/1-values/cmdlinedefinemissing.fail.6502 @@ -0,0 +1,2 @@ +\ beebasm -D V= +\ Check handling of missing value defined on the command-line with -D From b3df7af37f9e7ddb8e228c9b4fa731c6467629ab Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 21:04:04 +0100 Subject: [PATCH 138/144] Run tests on Ubuntu 22.04 and 20.04 and macOS 12 --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 778f792..9ba8bc1 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] cc: [ 'gcc', 'clang' ] name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} env: @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] name: Compile via Makefile on ${{ matrix.os }} steps: - uses: actions/checkout@v2 From c01aa48f3a6fd497b8774acabe3ba307d5405549 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 21:28:37 +0100 Subject: [PATCH 139/144] Revert "Run tests on Ubuntu 22.04 and 20.04 and macOS 12" This reverts commit b3df7af37f9e7ddb8e228c9b4fa731c6467629ab. --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 9ba8bc1..778f792 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] cc: [ 'gcc', 'clang' ] name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} env: @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] + os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] name: Compile via Makefile on ${{ matrix.os }} steps: - uses: actions/checkout@v2 From c5a0a3cfd7b08b0b2ba945b127418e2a15eb2496 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 21:04:04 +0100 Subject: [PATCH 140/144] Run tests on Ubuntu 22.04 and 20.04 and macOS 12 --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 778f792..9ba8bc1 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] cc: [ 'gcc', 'clang' ] name: Compile via CMakeLists.txt on ${{ matrix.os }} using ${{ matrix.cc }} env: @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-latest', 'macos-latest', 'ubuntu-18.04'] + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] name: Compile via Makefile on ${{ matrix.os }} steps: - uses: actions/checkout@v2 From 72c68019476eb08cdf967e25cf4e67e2e0573cda Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Wed, 31 Aug 2022 22:01:06 +0100 Subject: [PATCH 141/144] Modify string initialisation to placate the new compiler --- src/value.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/value.h b/src/value.h index a0e0040..ff853e2 100644 --- a/src/value.h +++ b/src/value.h @@ -40,7 +40,6 @@ struct StringHeader StringHeader* header = Allocate(length); char* buffer = StringBuffer(header); memcpy(buffer, text, length); - buffer[length] = 0; return header; } @@ -87,7 +86,6 @@ struct StringHeader char* buffer = StringBuffer(header); memcpy(buffer, StringData(header1), header1->m_length); memcpy(buffer + header1->m_length, StringData(header2), header2->m_length); - buffer[length] = 0; } return header; } @@ -121,7 +119,6 @@ struct StringHeader memcpy(buffer, sourceData, sourceLength); buffer += sourceLength; } - *buffer = 0; } return header; } @@ -167,9 +164,12 @@ struct StringHeader static StringHeader* Allocate(unsigned int length) { int fullLength = sizeof(StringHeader) + length + 1; - StringHeader* header = static_cast<StringHeader*>(malloc(fullLength)); - if (!header) + char* data = static_cast<char*>(malloc(fullLength)); + if (!data) return 0; + // Null-terminate the buffer + data[fullLength - 1] = 0; + StringHeader* header = reinterpret_cast<StringHeader*>(data); header->m_refCount = 0; header->m_length = length; return header; From c6fdb1f917e6424599d1c77d7682ea3187016be6 Mon Sep 17 00:00:00 2001 From: Chris Killpack <chris@chriskillpack.com> Date: Tue, 4 Oct 2022 16:15:09 -0700 Subject: [PATCH 142/144] Promote to v1.10 --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 17fd68a..05bd85f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,7 +42,7 @@ using namespace std; -#define VERSION "1.10rc2" +#define VERSION "1.10" /*************************************************************************************************/ From 5c9bb55f5dcbbb56dae919d8257b809bfdf3836b Mon Sep 17 00:00:00 2001 From: Chris Killpack <chris@chriskillpack.com> Date: Fri, 7 Oct 2022 17:11:16 -0700 Subject: [PATCH 143/144] Update README changelog Adds missing description of changes in v1.10 --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6783705..78419fb 100644 --- a/README.md +++ b/README.md @@ -732,16 +732,16 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` -??/??/???? 1.10 Documented "$" and "%" as literal prefixes (thanks to - cardboardguru76 for pointing this out). - Fixed silently treating label references starting with "." +08/10/2020 1.10 (Potentially breaking) Random number generator now uses 32-bit ints. + Documented "$" and "%" as literal prefixes (thanks to cardboardguru76 for pointing this out). + Fixed silently treating label references starting with "." as 0 (thanks to Charles Reilly for this fix). Allowed "-h" and "-help" options to see help. - Fixed tokenisation of BASIC pseudo-variables in some cases. - (Thanks to Richard Russell for advice on this.) + Fixed tokenisation of BASIC pseudo-variables in some cases. (Thanks to Richard Russell advice on this.) Fixed incorrect line number for errors inside macros with blank lines inside them. Fixed incorrect line numbers from PUTBASIC in some cases. + Fixed crashes in PUTBASIC caused by edge cases in end-of-file handling. Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) Added -cycle, -dd and -labels options (thanks to tricky for these) Added CMake support and man page (thanks to Dave Lambley) @@ -751,6 +751,8 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre Support underscore separators in numeric literals. C++'s built-in parsing was used previously so beebasm's numeric syntax varied between compilers and platforms; this is fixed. + Improved literal exponent parsing. + Error on out of range integer conversions. 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT From b2c3a534287ee11c5756cd8788c25a84c3ad8ea0 Mon Sep 17 00:00:00 2001 From: Charles Reilly <beebasm.2018@charlesreilly.com> Date: Sat, 8 Oct 2022 18:51:57 +0100 Subject: [PATCH 144/144] README - updated version, copyright, date and formatting --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 78419fb..e31b9df 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # BeebAsm -**Version V1.10-pre** +**Version V1.10** A portable 6502 assembler with BBC Micro style syntax -Copyright (C) Rich Talbot-Watkins 2007-2012 +Copyright (C) Rich Talbot-Watkins and the contributors 2007-2022 <richtw1@gmail.com> This program is free software: you can redistribute it and/or modify @@ -732,34 +732,34 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` -08/10/2020 1.10 (Potentially breaking) Random number generator now uses 32-bit ints. - Documented "$" and "%" as literal prefixes (thanks to cardboardguru76 for pointing this out). - Fixed silently treating label references starting with "." - as 0 (thanks to Charles Reilly for this fix). - Allowed "-h" and "-help" options to see help. - Fixed tokenisation of BASIC pseudo-variables in some cases. (Thanks to Richard Russell advice on this.) - Fixed incorrect line number for errors inside macros with - blank lines inside them. - Fixed incorrect line numbers from PUTBASIC in some cases. - Fixed crashes in PUTBASIC caused by edge cases in end-of-file handling. - Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) - Added -cycle, -dd and -labels options (thanks to tricky for these) - Added CMake support and man page (thanks to Dave Lambley) - Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, - LEFT$, RIGHT$, STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with - thanks to Steven Flintham.) - Support underscore separators in numeric literals. C++'s built-in - parsing was used previously so beebasm's numeric syntax varied - between compilers and platforms; this is fixed. - Improved literal exponent parsing. - Error on out of range integer conversions. +09/10/2022 1.10 (Potentially breaking) Random number generator now uses 32-bit ints. + Documented "$" and "%" as literal prefixes (thanks to cardboardguru76 for pointing this out). + Fixed silently treating label references starting with "." + as 0 (thanks to Charles Reilly for this fix). + Allowed "-h" and "-help" options to see help. + Fixed tokenisation of BASIC pseudo-variables in some cases. (Thanks to Richard Russell advice on this.) + Fixed incorrect line number for errors inside macros with + blank lines inside them. + Fixed incorrect line numbers from PUTBASIC in some cases. + Fixed crashes in PUTBASIC caused by edge cases in end-of-file handling. + Added FILELINE$ and CALLSTACK$ (thanks to tricky for this) + Added -cycle, -dd and -labels options (thanks to tricky for these) + Added CMake support and man page (thanks to Dave Lambley) + Added string values and VAL, EVAL, STR$, LEN, CHR$, ASC, MID$, + LEFT$, RIGHT$, STRING$, LOWER$, UPPER$, ASM. (Charles Reilly with + thanks to Steven Flintham.) + Support underscore separators in numeric literals. C++'s built-in + parsing was used previously so beebasm's numeric syntax varied + between compilers and platforms; this is fixed. + Improved literal exponent parsing. + Error on out of range integer conversions. 12/05/2018 1.09 Added ASSERT Added CPU (as a constant) Added PUTTEXT Added RANDOMIZE Added TIME$ Added command line options: -title, -vc, -w, -D - Added conditional assignment (=?) + Added conditional assignment (=?) Improved error handling in PUTFILE/PUTBASIC Added optional automatic line numbering for PUTBASIC Show a call stack when an error occurs inside a macro