diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..73f0f5f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# https://editorconfig.org/ + +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.{c,h,cpp}] +indent_style = tab +tab_width = 4 diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..9ba8bc1 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,43 @@ +name: Build and run tests + +on: [push, pull_request] + +jobs: + cmake: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] + cc: [ 'gcc', 'clang' ] + 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: + - uses: actions/checkout@v2 + - run: cmake . + - run: make + - run: make test CTEST_OUTPUT_ON_FAILURE=TRUE + make: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-22.04', 'macos-12', 'ubuntu-20.04'] + name: Compile via Makefile on ${{ matrix.os }} + 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 -v 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/.gitignore b/.gitignore index cbf1c13..18c63d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ *.swp /beebasm /src/objects +test/testlog.txt +test/**/test +test/**/test.ssd +test/**/testgold.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0e2b006 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) + +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) + +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 -v) diff --git a/README.md b/README.md index 4be6a5a..e31b9df 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # BeebAsm -**Version V1.09** +**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 This program is free software: you can redistribute it and/or modify @@ -135,13 +135,22 @@ 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. +`-cycle ` + +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 ` 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! `-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` @@ -151,6 +160,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 ` + +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.: @@ -167,10 +184,12 @@ Things like `STA&4000` are permitted with or without `-w`. `-D =` +`-S =` + Define `` before starting to assemble. If `` is not given, `-1` (`TRUE`) -will be used by default. Note that there must be a space between `-D` and the symbol. `` 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. `` may be in decimal, hexadecimal (prefixed with $, & or 0x) or binary (prefixed with %). `` 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 @@ -182,7 +201,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. 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: @@ -223,8 +242,29 @@ 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 +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 +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 +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() ``` +The assembly date/time is constant throughout the assembly; every use of `TIME$` +will return the same date/time. + Also, some constants are defined: ``` @@ -236,25 +276,13 @@ 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). 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 and "ctr" for the `-D`/conditional assignment support.) +(Thanks to Stephen Harris and Charles Reilly for the `-D`/conditional assignment support.) ## 6. ASSEMBLER DIRECTIVES @@ -288,6 +316,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. @@ -358,9 +391,9 @@ Puts a 'guard' on the specified address which will cause an error if you attempt Clears all guards between the `` and ` 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. @@ -378,6 +411,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"` @@ -608,6 +643,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 ` + +Assemble the supplied assembly language string. 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. @@ -693,20 +732,41 @@ There is also a demo called `"relocdemo.asm"`, which shows how the 'reload addre ## 9. VERSION HISTORY ``` +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 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. 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 +.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 +Specify source filename +.TP +\fB\-o\fR +Specify output filename (when not specified by SAVE command) +.TP +\fB\-di\fR +Specify a disc image file to be added to +.TP +\fB\-do\fR +Specify a disc image file to output +.TP +\fB\-boot\fR +Specify a filename to be run by !BOOT on a new disc image +.TP +\fB\-opt\fR +Specify the *OPT 4,n for the generated disc image +.HP +\fB\-title\fR 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 + diff --git a/beebasm.exe b/beebasm.exe deleted file mode 100644 index f77e25c..0000000 Binary files a/beebasm.exe and /dev/null differ 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 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.) 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/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") diff --git a/src/BASIC.cpp b/src/BASIC.cpp index ff95f62..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; } @@ -587,6 +591,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 +682,7 @@ bool EncodeLine() } } - if( - (Flags & 0x40) && StartOfStatement - ) + if((Flags & 0x40) && StartOfStatement) { /* pseudo-variable flag */ Memory[Addr-1] += 0x40; //adjust just-written token @@ -694,7 +698,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; } @@ -753,7 +758,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? */ @@ -796,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<Uint8>(Length); diff --git a/src/Makefile b/src/Makefile index cf471ec..4f31093 100644 --- a/src/Makefile +++ b/src/Makefile @@ -53,6 +53,9 @@ 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..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 @@ -168,6 +170,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 +195,11 @@ run: $(VB)$(TARGET) $(PARAMS) -all: code run +test: + $(VB)$(TEST) $(VB_TEST) + + +all: code run test clean: diff --git a/src/VS2010/BeebAsm.vcxproj b/src/VS2010/BeebAsm.vcxproj index 739c174..60fab5e 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"> @@ -82,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" /> @@ -98,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" /> @@ -106,6 +112,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..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"> @@ -107,5 +110,11 @@ <ClInclude Include="..\constants.h"> <Filter>Header Files</Filter> </ClInclude> + <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/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 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" 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 <iostream> #include <cassert> -#include <sstream> #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/asmexception.h b/src/asmexception.h index c7eadc8..7789a9f 100644 --- a/src/asmexception.h +++ b/src/asmexception.h @@ -201,12 +201,13 @@ 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." ); 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)." ); @@ -219,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." ); @@ -248,6 +250,8 @@ 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." ); +DEFINE_SYNTAX_EXCEPTION( OutOfIntegerRange, "Number out of range for a 32-bit integer." ); diff --git a/src/assemble.cpp b/src/assemble.cpp index 855b381..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; @@ -117,6 +118,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 +141,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 +174,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,7 +197,6 @@ int LineParser::GetInstructionAndAdvanceColumn() } - /*************************************************************************************************/ /** LineParser::HasAddressingMode() @@ -220,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() << " "; @@ -259,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() << " "; @@ -333,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 df961ef..c53a8a2 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -22,6 +22,7 @@ */ /*************************************************************************************************/ +#include <algorithm> #include <iostream> #include <iomanip> #include <string> @@ -31,6 +32,7 @@ #include "lineparser.h" #include "globaldata.h" #include "objectcode.h" +#include "stringutils.h" #include "symboltable.h" #include "sourcefile.h" #include "asmexception.h" @@ -79,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 } }; @@ -101,33 +104,346 @@ 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++ ) + { + if ( token[ j ] != toupper( m_line[ m_column + j ] ) ) + { + bMatch = false; + break; + } + } + + if ( bMatch ) { - bMatch = false; - break; + m_column += len; + return i; } } + } + + return -1; +} + + + +// 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; - if ( bMatch ) + 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) + { + } + 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 + { + 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<T>& 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<T>& 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<T>& 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<T>& AcceptUndef() + { + if (m_state == StateUndefined) { - m_column += len; - return i; + m_state = StateFound; + m_value = 0; } + return *this; } +private: + // Prevent assignment + Argument<T> operator=(const Argument<T>& that); - return -1; + string m_line; + int m_column; + State m_state; + T m_value; +}; + +typedef Argument<int> IntArg; +typedef Argument<double> DoubleArg; +typedef Argument<string> StringArg; +typedef Argument<Value> ValueArg; + +template<> StringArg& StringArg::Default(string value) +{ + if ( !Found() ) + { + m_value = value; + m_state = StateFound; + } + return *this; } +// AcceptUndef should not be called for string types. +template<> 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>(&ArgListParser::ConvertDoubleToInt); + } + + DoubleArg ParseDouble() + { + return ParseNumber<DoubleArg>(&ArgListParser::ConvertDoubleToDouble); + } + + 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); + + 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() ) + { + 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, (this->*convertDoubleTo)(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; +}; + /*************************************************************************************************/ @@ -201,15 +517,18 @@ 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 ); } + + SymbolTable::Instance().AddLabel(symbolName); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "." << symbolName << endl; } @@ -271,20 +590,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 ); - } } @@ -296,19 +607,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 ); - } } @@ -320,19 +623,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 ); - } } @@ -344,33 +639,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 ); - } } @@ -384,48 +660,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 ); } @@ -433,12 +679,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 ); } @@ -506,7 +747,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; @@ -542,17 +783,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 ) @@ -568,12 +805,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 ); - } } @@ -591,37 +822,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 ) + if ( m_sourceCode->ShouldOutputAsm() ) { - throw AsmException_SyntaxError_MissingQuote( m_line, m_line.length() ); - } - else - { - 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() ); - input.Process(); + cerr << "Including file " << filename << endl; } - m_column = endQuotePos + 1; + SourceFile input( filename.c_str(), m_sourceCode ); + input.Process(); if ( AdvanceAndCheckEndOfStatement() ) { @@ -638,41 +847,19 @@ void LineParser::HandleInclude() /*************************************************************************************************/ void LineParser::HandleIncBin() { - 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 ) + 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 ); @@ -688,114 +875,38 @@ void LineParser::HandleIncBin() /*************************************************************************************************/ void LineParser::HandleEqub() { + ArgListParser args(*this); + + Value value = args.ParseValue().AcceptUndef(); + do { - if ( !AdvanceAndCheckEndOfStatement() ) + if (value.GetType() == Value::StringValue) { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); + // handle equs + HandleEqus( value.GetString() ); } - - // handle TIME$ (special case of string) - - if ( m_column + 4 < m_line.length() && m_line.substr( m_column, 5 ) == "TIME$" ) - { - 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 ); - } - - // handle string - - else if ( m_column < m_line.length() && 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_line.length() ); - } - else - { - string equs( m_line.substr( m_column + 1, endQuotePos - m_column - 1 ) ); - HandleEqus( equs ); - } - - m_column = endQuotePos + 1; - } - 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 ); } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { 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 ) { @@ -804,42 +915,43 @@ void LineParser::HandleEqub() throw; } } - - if ( !AdvanceAndCheckEndOfStatement() ) + else { - break; + // Unknown value type; this should never happen. + assert(false); } - if ( m_column >= m_line.length() || m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + ValueArg arg = args.ParseValue().AcceptUndef(); + if (!arg.Found()) + break; - m_column++; + value = arg; } while ( true ); + + args.CheckComplete(); } /*************************************************************************************************/ /** - 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() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; 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 ] ); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { if ( i < 3 ) { @@ -864,7 +976,7 @@ void LineParser::HandleEqus( const string& equs ) } } - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << endl << nouppercase << dec << setfill( ' ' ); } @@ -879,32 +991,13 @@ 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); - if ( GlobalData::Instance().ShouldOutputAsm() ) + do + { + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -925,24 +1018,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(); } @@ -954,27 +1038,13 @@ 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(); - if ( GlobalData::Instance().ShouldOutputAsm() ) + do + { + if ( m_sourceCode->ShouldOutputAsm() ) { cout << uppercase << hex << setfill( '0' ) << " "; cout << setw(4) << ObjectCode::Instance().GetPC() << " "; @@ -999,24 +1069,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(); } @@ -1088,140 +1149,28 @@ void LineParser::HandleAssert() /*************************************************************************************************/ void LineParser::HandleSave() { - int start = 0; - int end = 0; - int exec = 0; - int reload = 0; + // syntax is SAVE ["filename"], start, end [, exec [, reload] ] - int oldColumn = m_column; + ArgListParser args(*this); - // syntax is SAVE "filename", start, end [, exec [, reload] ] + 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(); - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - string saveFile; - - 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++; - } - - // get start address - - start = EvaluateExpressionAsInt(); - 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 { @@ -1231,11 +1180,13 @@ void LineParser::HandleSave() } else { - throw AsmException_SyntaxError_NoAnonSave( m_line, oldColumn ); + throw AsmException_SyntaxError_NoAnonSave( m_line, saveParam.Column() ); } } - if ( GlobalData::Instance().ShouldOutputAsm() ) + string saveFile = saveParam; + + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "Saving file '" << saveFile << "'" << endl; } @@ -1283,55 +1234,10 @@ void LineParser::HandleSave() /** LineParser::HandleFor() */ -/*************************************************************************************************/ -void LineParser::HandleFor() -{ - // syntax is FOR variable, exp, exp [, exp] - - if ( !AdvanceAndCheckEndOfStatement() ) - { - // found nothing - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - // first look for the variable name - - if ( !isalpha( m_line[ m_column ] ) && m_line[ m_column ] != '_' ) - { - throw AsmException_SyntaxError_InvalidSymbolName( m_line, m_column ); - } - - // Symbol starts with a valid character - - int oldColumn = m_column; - string symbolName = GetSymbolName() + m_sourceCode->GetSymbolNameSuffix(); - - // Check variable has not yet been defined - - if ( SymbolTable::Instance().IsSymbolDefined( symbolName ) ) - { - throw AsmException_SyntaxError_LabelAlreadyDefined( m_line, oldColumn ); - } - - // look for first 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 start value - - double start = EvaluateExpression(); - - // look for comma +/*************************************************************************************************/ +void LineParser::HandleFor() +{ + // syntax is FOR variable, exp, exp [, exp] if ( !AdvanceAndCheckEndOfStatement() ) { @@ -1339,44 +1245,34 @@ void LineParser::HandleFor() throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); } - if ( m_line[ m_column ] != ',' ) + // first look for the variable name + + if ( !isalpha( m_line[ m_column ] ) && m_line[ m_column ] != '_' ) { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + throw AsmException_SyntaxError_InvalidSymbolName( m_line, m_column ); } - m_column++; - - // look for end value + // Symbol starts with a valid character - double end = EvaluateExpression(); + int oldColumn = m_column; + string symbolName = GetSymbolName() + m_sourceCode->GetSymbolNameSuffix(); - double step = 1.0; + // Check variable has not yet been defined - if ( AdvanceAndCheckEndOfStatement() ) + if ( SymbolTable::Instance().IsSymbolDefined( symbolName ) ) { - // look for step variable - - if ( m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } - - m_column++; - - step = EvaluateExpression(); - - if ( step == 0.0 ) - { - throw AsmException_SyntaxError_BadStep( m_line, m_column ); - } - - // check this is now the end + throw AsmException_SyntaxError_LabelAlreadyDefined( m_line, oldColumn ); + } - if ( AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + ArgListParser args(*this, true); + double start = args.ParseDouble(); + double end = args.ParseDouble(); + double step = args.ParseDouble().Default(1); + args.CheckComplete(); + if ( step == 0.0 ) + { + throw AsmException_SyntaxError_BadStep( m_line, m_column ); } m_sourceCode->AddFor( symbolName, @@ -1484,26 +1380,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 @@ -1534,29 +1410,68 @@ void LineParser::HandlePrint() } else { - // print in dec + StringUtils::EatWhitespace( m_line, m_column ); + const char* filelineKeyword = "FILELINE$"; + const int filelineKeywordLength = 9; + const char* callstackKeyword = "CALLSTACK$"; + const int callstackKeywordLength = 10; - 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 ( !strncmp( m_line.c_str() + m_column, callstackKeyword, callstackKeywordLength ) ) { - if ( GlobalData::Instance().IsFirstPass() ) + if ( !GlobalData::Instance().IsFirstPass() ) { - value = 0.0; + 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() ); + } } - else + m_column += callstackKeywordLength; + } + else + { + // print number in decimal or string + + Value value; + + try { - throw; + value = EvaluateExpression(); + } + catch ( AsmException_SyntaxError_SymbolNotDefined& ) + { + if ( GlobalData::Instance().IsSecondPass() ) + { + throw; + } } - } - if ( GlobalData::Instance().IsSecondPass() ) - { - cout << value << " "; + if ( GlobalData::Instance().IsSecondPass() ) + { + if (value.GetType() == Value::NumberValue) + { + StringUtils::PrintNumber(cout, value.GetNumber()); + cout << " "; + } + else if (value.GetType() == Value::StringValue) + { + String text = value.GetString(); + const char* pstr = text.Text(); + for (unsigned int i = 0; i != text.Length(); ++i) + { + cout << *pstr; + ++pstr; + } + } + } } } } @@ -1603,128 +1518,14 @@ 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 beebFilename = hostFilename; - int start = 0; - int exec = 0; - - m_column = endQuotePos + 1; - - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( m_line[ m_column ] != ',' ) - { - throw AsmException_SyntaxError_MissingComma( m_line, m_column ); - } - - m_column++; - - if ( !AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_EmptyExpression( m_line, m_column ); - } - - if ( 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 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 ); - } - - if ( 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; - } - } - - exec = start; - - if ( start < 0 || start > 0xFFFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } - - 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 ); - } - } + ArgListParser args(*this); - // check this is now the end + 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); - if ( AdvanceAndCheckEndOfStatement() ) - { - throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); - } + args.CheckComplete(); if ( GlobalData::Instance().IsSecondPass() ) { @@ -1754,7 +1555,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 ); @@ -1797,29 +1598,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 @@ -1831,29 +1612,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 @@ -2011,38 +1770,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 ); } @@ -2053,49 +1791,23 @@ void LineParser::HandleError() /*************************************************************************************************/ void LineParser::HandleCopyBlock() { - 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 ); - } - - 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 ); - } + ArgListParser args(*this); - m_column++; + int start = args.ParseInt().Range(0, 0xFFFF); + int end = args.ParseInt().Range(0, 0xFFFF); + int dest = args.ParseInt().Range(0, 0xFFFF); - int dest = EvaluateExpressionAsInt(); - if ( dest < 0 || dest > 0xFFFF ) - { - throw AsmException_SyntaxError_OutOfRange( m_line, m_column ); - } + args.CheckComplete(); - if ( GlobalData::Instance().IsSecondPass() ) + try { - ObjectCode::Instance().CopyBlock( start, end, dest ); + ObjectCode::Instance().CopyBlock( start, end, dest, GlobalData::Instance().IsFirstPass() ); } - - if ( m_column < m_line.length() && m_line[ m_column ] == ',' ) + catch ( AsmException_AssembleError& e ) { - // Unexpected comma (remembering that an expression can validly end with a comma) - throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); + e.SetString( m_line ); + e.SetColumn( m_column ); + throw; } } @@ -2133,3 +1845,34 @@ void LineParser::HandleRandomize() throw AsmException_SyntaxError_UnexpectedComma( m_line, m_column ); } } + + +/*************************************************************************************************/ +/** + LineParser::HandleAsm() +*/ +/*************************************************************************************************/ +void LineParser::HandleAsm() +{ + // look for assembly language string + + string assembly = EvaluateExpressionAsString(); + + // check this is now the end + + if ( AdvanceAndCheckEndOfStatement() ) + { + throw AsmException_SyntaxError_InvalidCharacter( m_line, m_column ); + } + + LineParser parser(m_sourceCode, assembly); + + // Parse the mnemonic, don't require a non-alpha after it. + int instruction = parser.GetInstructionAndAdvanceColumn(false); + if (instruction < 0) + { + throw AsmException_SyntaxError_MissingAssemblyInstruction( parser.m_line, parser.m_column ); + } + + parser.HandleAssembler(instruction); +} diff --git a/src/discimage.cpp b/src/discimage.cpp index bbed4b2..7fff2e4 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().GetDiscCycle(); m_aCatalog[ 0x106 ] = 0x03 | ( ( GlobalData::Instance().GetDiscOption() & 3 ) << 4); m_aCatalog[ 0x107 ] = 0x20; diff --git a/src/expression.cpp b/src/expression.cpp index 7659539..661b412 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" @@ -37,7 +38,8 @@ #include "sourcefile.h" #include "random.h" #include "constants.h" - +#include "stringutils.h" +#include "literals.h" using namespace std; @@ -45,62 +47,77 @@ 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 } + { "(", -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 }, + { "STR$~(", 10, 1, &LineParser::EvalStrHex }, + { "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 }, + { "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 } }; @@ -112,76 +129,21 @@ 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; - - if ( m_column < m_line.length() && ( isdigit( m_line[ m_column ] ) || m_line[ m_column ] == '.' ) ) - { - // get a number - - istringstream str( m_line ); - str.seekg( m_column ); - str >> value; - m_column = static_cast< size_t >( str.tellg() ); - } - else if ( m_column < m_line.length() && ( m_line[ m_column ] == '&' || m_line[ m_column ] == '$' ) ) - { - // get a hex digit - - m_column++; + Value value; - 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; - - istringstream str( m_line ); - str.seekg( m_column ); - str >> hex >> hexValue; - m_column = static_cast< size_t >( str.tellg() ); - - value = static_cast< double >( hexValue ); - } - } - else if ( m_column < m_line.length() && m_line[ m_column ] == '%' ) + double double_value; + if ( Literals::ParseNumeric(m_line, m_column, double_value) ) { - // get binary - - m_column++; - - if ( m_column >= m_line.length() || ( m_line[ m_column ] != '0' && m_line[ m_column ] != '1' ) ) - { - // badly formed bin literal - throw AsmException_SyntaxError_BadBin( m_line, m_column ); - } - else - { - // parse binary number - - int binValue = 0; - - 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' ) ); - - value = static_cast< double >( binValue ); - } + value = double_value; } else if ( m_column < m_line.length() && m_line[ m_column ] == '*' ) { @@ -203,31 +165,63 @@ 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 (symbolName == "TIME$") { - string fullSymbolName = symbolName + m_sourceCode->GetSymbolNameSuffix( forLevel ); + // Handle TIME$ with no parameters + value = FormatAssemblyTime("%a,%d %b %Y.%H:%M:%S"); + } + else + { + // Regular symbol - if ( SymbolTable::Instance().IsSymbolDefined( fullSymbolName ) ) + if ( !m_sourceCode->GetSymbolValue(symbolName, value) ) { - value = SymbolTable::Instance().GetSymbol( fullSymbolName ); - bFoundSymbol = true; - break; + // symbol not known + throw AsmException_SyntaxError_SymbolNotDefined( m_line, oldColumn ); } } - - if ( !bFoundSymbol ) - { - // symbol not known - throw AsmException_SyntaxError_SymbolNotDefined( m_line, oldColumn ); - } } else { @@ -247,7 +241,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 @@ -258,11 +252,15 @@ double 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 ) { @@ -300,6 +298,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) if ( len > 1 && token[ len - 1 ] == '(' ) { + pendingCommaCount = m_gaUnaryOperatorTable[ matchedToken ].parameterCount - 1; m_column--; assert( m_line[ m_column ] == '(' ); } @@ -317,7 +316,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) throw AsmException_SyntaxError_ExpressionTooComplex( m_line, m_column ); } - double value; + Value value; try { @@ -346,7 +345,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) { // If unary operator *was* found... - const Operator& thisOp = m_gaUnaryOperatorTable[ matchedToken ]; + Operator thisOp = m_gaUnaryOperatorTable[ matchedToken ]; if ( thisOp.handler != NULL ) { @@ -365,6 +364,9 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) } else { + // The open bracket's parameterCount counts down the number of commas expected. + thisOp.parameterCount = pendingCommaCount; + pendingCommaCount = 0; bracketCount++; } @@ -444,9 +446,14 @@ double 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; @@ -466,8 +473,39 @@ double 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, @@ -517,6 +555,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(); +} /*************************************************************************************************/ @@ -528,7 +582,7 @@ double LineParser::EvaluateExpression( bool bAllowOneMismatchedCloseBracket ) /*************************************************************************************************/ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< int >( EvaluateExpression( bAllowOneMismatchedCloseBracket ) ); + return ConvertDoubleToInt( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ); } @@ -541,57 +595,214 @@ int LineParser::EvaluateExpressionAsInt( bool bAllowOneMismatchedCloseBracket ) /*************************************************************************************************/ unsigned int LineParser::EvaluateExpressionAsUnsignedInt( bool bAllowOneMismatchedCloseBracket ) { - return static_cast< unsigned int >( EvaluateExpression( bAllowOneMismatchedCloseBracket ) ); + return static_cast< unsigned int >( ConvertDoubleToInt( EvaluateExpressionAsDouble( bAllowOneMismatchedCloseBracket ) ) ); } +/*************************************************************************************************/ +/** + 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::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::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::EvalSubtract() + LineParser::StackTopNumber() + + Retrieve a number from the top of the stack, or throw an exception */ /*************************************************************************************************/ -void LineParser::EvalSubtract() +double LineParser::StackTopNumber() { - if ( m_valueStackPtr < 2 ) + if ( m_valueStackPtr < 1 ) { 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 value = m_valueStack[ m_valueStackPtr - 1 ]; + if (value.GetType() != Value::NumberValue) + { + throw AsmException_SyntaxError_TypeMismatch( m_line, m_column ); + } + return value.GetNumber(); } +/*************************************************************************************************/ +/** + 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::EvalMultiply() + LineParser::StackTopTwoNumbers() + + Retrieve two values of numeric type, or throw an exception */ /*************************************************************************************************/ -void LineParser::EvalMultiply() +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>(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 ); + } +} + +/*************************************************************************************************/ +/** + 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--; +} + + + +/*************************************************************************************************/ +/** + LineParser::EvalMultiply() +*/ +/*************************************************************************************************/ +void LineParser::EvalMultiply() +{ + std::pair<double, double> values = StackTopTwoNumbers(); + m_valueStack[ m_valueStackPtr - 2 ] = values.first * values.second; m_valueStackPtr--; } @@ -604,15 +815,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--; } @@ -625,11 +833,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 ) @@ -652,17 +857,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--; } @@ -675,17 +876,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--; } @@ -693,17 +890,37 @@ void LineParser::EvalMod() /*************************************************************************************************/ /** - LineParser::EvalShiftLeft() + Shifting helper functions */ /*************************************************************************************************/ -void LineParser::EvalShiftLeft() +static int LogicalShiftLeft(int value, unsigned int shift) { - if ( m_valueStackPtr < 2 ) + 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) { - throw AsmException_SyntaxError_MissingValue( m_line, m_column ); + const unsigned int bitcount = 8 * sizeof(unsigned int); + const unsigned int ones = ~0; + shifted |= (ones << (bitcount - shift)); } - int val = static_cast< int >( m_valueStack[ m_valueStackPtr - 2 ] ); - int shift = static_cast< int >( m_valueStack[ m_valueStackPtr - 1 ] ); + return static_cast<int>(shifted); +} + +/*************************************************************************************************/ +/** + LineParser::EvalShiftLeft() +*/ +/*************************************************************************************************/ +void LineParser::EvalShiftLeft() +{ + std::pair<int, int> values = StackTopTwoInts(); + + int val = values.first; + int shift = values.second; int result; if ( shift > 31 || shift < -31 ) @@ -712,7 +929,7 @@ void LineParser::EvalShiftLeft() } else if ( shift > 0 ) { - result = val << shift; + result = LogicalShiftLeft(val, static_cast<unsigned int>(shift)); } else if ( shift == 0 ) { @@ -720,7 +937,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 ); @@ -736,12 +953,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 ) @@ -750,7 +965,7 @@ void LineParser::EvalShiftRight() } else if ( shift > 0 ) { - result = val >> shift; + result = ArithmeticShiftRight(val, static_cast<unsigned int>(shift)); } else if ( shift == 0 ) { @@ -758,7 +973,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 ); @@ -774,13 +989,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--; } @@ -793,13 +1003,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--; } @@ -812,13 +1017,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--; } @@ -831,11 +1031,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--; } @@ -848,11 +1045,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--; } @@ -865,11 +1059,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--; } @@ -882,11 +1073,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--; } @@ -899,11 +1087,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--; } @@ -916,11 +1101,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--; } @@ -933,11 +1115,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(); } @@ -949,12 +1127,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 = ~StackTopInt(); + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -982,12 +1156,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 = StackTopInt() & 0xFF; + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -999,12 +1169,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 = (StackTopInt() & 0xffff) >> 8; + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >(value); } @@ -1016,11 +1182,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() ); } @@ -1032,11 +1194,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() ); } @@ -1048,11 +1206,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() ); } @@ -1064,11 +1218,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 ) { @@ -1085,11 +1235,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 ) { @@ -1106,11 +1252,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 ) { @@ -1127,11 +1269,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 ) { @@ -1148,11 +1286,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 ) { @@ -1169,11 +1303,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 ) { @@ -1190,11 +1320,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 ) { @@ -1211,11 +1337,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; } @@ -1227,11 +1349,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; } @@ -1243,12 +1361,7 @@ 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 ] ) ); + m_valueStack[ m_valueStackPtr - 1 ] = static_cast< double >( StackTopInt() ); } @@ -1260,11 +1373,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() ); } @@ -1276,12 +1385,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 ); } @@ -1294,12 +1398,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 ) @@ -1312,8 +1411,290 @@ 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; } + + +/*************************************************************************************************/ +/** + LineParser::EvalTime() +*/ +/*************************************************************************************************/ +void LineParser::EvalTime() +{ + m_valueStack[ m_valueStackPtr - 1 ] = FormatAssemblyTime(StackTopString().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); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalStr() +*/ +/*************************************************************************************************/ +void LineParser::EvalStr() +{ + ostringstream stream; + StringUtils::PrintNumber(stream, StackTopNumber()); + string result = stream.str(); + + m_valueStack[ m_valueStackPtr - 1 ] = String(result.data(), result.length()); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalStrHex() +*/ +/*************************************************************************************************/ +void LineParser::EvalStrHex() +{ + ostringstream stream; + stream << std::hex << std::uppercase << StackTopInt(); + 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; +} + + +/*************************************************************************************************/ +/** + LineParser::EvalEval() +*/ +/*************************************************************************************************/ +void LineParser::EvalEval() +{ + String expr = StackTopString(); + LineParser parser(m_sourceCode, string(expr.Text(), expr.Length())); + Value result = parser.EvaluateExpression(); + m_valueStack[ m_valueStackPtr - 1 ] = result; +} + + +/*************************************************************************************************/ +/** + LineParser::EvalLen() +*/ +/*************************************************************************************************/ +void LineParser::EvalLen() +{ + String str = StackTopString(); + m_valueStack[ m_valueStackPtr - 1 ] = str.Length(); +} + + +/*************************************************************************************************/ +/** + LineParser::EvalChr() +*/ +/*************************************************************************************************/ +void LineParser::EvalChr() +{ + int ascii = StackTopInt(); + 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 = 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 ); + } + + m_valueStack[ m_valueStackPtr - 1 ] = text.SubString(index, length); +} + + +/*************************************************************************************************/ +/** + 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 = ConvertDoubleToInt(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 = ConvertDoubleToInt(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() +*/ +/*************************************************************************************************/ +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 = ConvertDoubleToInt(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); +} + + +/*************************************************************************************************/ +/** + 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/globaldata.cpp b/src/globaldata.cpp index b68e0b3..2de7f17 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 ), @@ -76,6 +77,7 @@ GlobalData::GlobalData() m_pOutputFile( NULL ), m_numAnonSaves( 0 ), m_discOption( 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 25a4f98..8d439c9 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; } @@ -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 SetDiscCycle( int num ) { m_discCycle = num; } inline void SetDiscTitle( const std::string& t ) { m_discTitle = t; } inline void SetRequireDistinctOpcodes ( bool b ) @@ -59,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; } @@ -68,6 +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 GetDiscCycle() const { return m_discCycle; } inline const std::string& GetDiscTitle() const { return m_discTitle; } inline time_t GetAssemblyTime() const { return m_assemblyTime; } @@ -83,6 +86,7 @@ class GlobalData int m_pass; const char* m_pBootFile; + bool m_bVerboseSet; bool m_bVerbose; bool m_bUseDiscImage; DiscImage* m_pDiscImage; @@ -91,6 +95,7 @@ class GlobalData const char* m_pOutputFile; int m_numAnonSaves; int m_discOption; + int m_discCycle; std::string m_discTitle; time_t m_assemblyTime; bool m_bRequireDistinctOpcodes; diff --git a/src/lineparser.cpp b/src/lineparser.cpp index 398cd63..ce8028a 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; @@ -96,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() ) { @@ -174,7 +178,7 @@ void LineParser::Process() m_column++; } - double value = EvaluateExpression(); + Value value = EvaluateExpression(); if ( GlobalData::Instance().IsFirstPass() ) { @@ -210,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; } @@ -225,7 +229,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() ) @@ -236,7 +240,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 ); } } @@ -269,7 +273,7 @@ void LineParser::Process() HandleCloseBrace(); - if ( GlobalData::Instance().ShouldOutputAsm() ) + if ( m_sourceCode->ShouldOutputAsm() ) { cout << "End macro " << macroName << endl; } @@ -282,6 +286,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(); + } + } } @@ -314,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 ] == '\'' ) @@ -357,22 +375,19 @@ void LineParser::SkipStatement() /*************************************************************************************************/ void LineParser::SkipExpression( int bracketCount, bool bAllowOneMismatchedCloseBracket ) { - while ( AdvanceAndCheckEndOfSubStatement() ) + 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; } } @@ -500,9 +515,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( ";:\\{}" ); + } } @@ -529,8 +551,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; } diff --git a/src/lineparser.h b/src/lineparser.h index a793e34..294bd13 100644 --- a/src/lineparser.h +++ b/src/lineparser.h @@ -24,6 +24,7 @@ #define LINEPARSER_H_ #include <string> +#include "value.h" class SourceCode; @@ -90,6 +91,7 @@ class LineParser { const char* token; int precedence; + int parameterCount; OperatorHandler handler; }; @@ -105,11 +107,12 @@ class LineParser int GetTokenAndAdvanceColumn(); void HandleToken( int i, int oldColumn ); int GetInstructionAndAdvanceColumn(); + int GetInstructionAndAdvanceColumn(bool requireDistinctOpcodes); int CheckMacroMatches(); 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(); @@ -135,7 +138,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(); @@ -160,13 +163,25 @@ class LineParser void HandleError(); void HandleCopyBlock(); void HandleRandomize(); + void HandleAsm(); // 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(); + std::string EvaluateExpressionAsString( bool bAllowOneMismatchedCloseBracket = false ); + Value GetValue(); + + // convenience functions for getting operator parameters from the stack + 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(); @@ -208,7 +223,22 @@ class LineParser void EvalLog(); void EvalLn(); void EvalExp(); - + void EvalTime(); + void EvalStr(); + void EvalStrHex(); + void EvalVal(); + void EvalEval(); + void EvalLen(); + void EvalChr(); + void EvalAsc(); + void EvalMid(); + void EvalLeft(); + void EvalRight(); + void EvalString(); + void EvalUpper(); + void EvalLower(); + + Value FormatAssemblyTime(const char* formatString); SourceCode* m_sourceCode; std::string m_line; @@ -222,10 +252,12 @@ 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; + + friend class ArgListParser; }; diff --git a/src/literals.cpp b/src/literals.cpp new file mode 100644 index 0000000..1aae858 --- /dev/null +++ b/src/literals.cpp @@ -0,0 +1,254 @@ +/*************************************************************************************************/ +/** + 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] == '.' || 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); + + // 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 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('e'); + index++; + if ( 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/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 2320893..05bd85f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,7 +42,7 @@ using namespace std; -#define VERSION "1.09" +#define VERSION "1.10" /*************************************************************************************************/ @@ -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 { @@ -73,11 +74,15 @@ int main( int argc, char* argv[] ) WAITING_FOR_BOOT_FILENAME, WAITING_FOR_DISC_OPTION, WAITING_FOR_DISC_TITLE, - WAITING_FOR_SYMBOL + WAITING_FOR_DISC_CYCLE, + WAITING_FOR_SYMBOL, + WAITING_FOR_STRING_SYMBOL, + WAITING_FOR_LABELS_FILE } state = READY; bool bDumpSymbols = false; + bool bDumpAllSymbols = false; GlobalData::Create(); SymbolTable::Create(); @@ -110,6 +115,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; @@ -118,6 +127,10 @@ int main( int argc, char* argv[] ) { state = WAITING_FOR_DISC_TITLE; } + else if ( strcmp( argv[i], "-cycle" ) == 0 ) + { + state = WAITING_FOR_DISC_CYCLE; + } else if ( strcmp( argv[i], "-w" ) == 0 ) { GlobalData::Instance().SetRequireDistinctOpcodes( true ); @@ -130,15 +143,29 @@ 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; } + else if ( strcmp( argv[i], "-dd" ) == 0 ) + { + bDumpAllSymbols = true; + } else if ( strcmp( argv[i], "-D" ) == 0 ) { state = WAITING_FOR_SYMBOL; } - else if ( strcmp( argv[i], "--help" ) == 0 ) + 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 ) ) { cout << "beebasm " VERSION << endl << endl; cout << "Possible options:" << endl; @@ -147,13 +174,17 @@ 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 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 << " -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; 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; } @@ -219,6 +250,12 @@ int main( int argc, char* argv[] ) state = READY; break; + case WAITING_FOR_DISC_CYCLE: + + GlobalData::Instance().SetDiscCycle( std::strtol( argv[i], NULL, 10 ) ); + state = READY; + break; + case WAITING_FOR_SYMBOL: if ( ! SymbolTable::Instance().AddCommandLineSymbol( argv[i] ) ) @@ -228,6 +265,22 @@ 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]; + state = READY; + break; } } @@ -280,7 +333,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(); } } @@ -292,12 +345,12 @@ 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 ) + 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 0a9beac..478e545 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-- ) { @@ -479,3 +491,23 @@ void ObjectCode::CopyBlock( int start, int end, int dest ) } } } + +#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a[0])) + +/*************************************************************************************************/ +/** + ObjectCode::AnyUsed() - is any memory USED? +*/ +/*************************************************************************************************/ +bool ObjectCode::AnyUsed() const +{ + for (unsigned 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 3220f1c..db6414b 100644 --- a/src/objectcode.h +++ b/src/objectcode.h @@ -57,15 +57,22 @@ 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 ); + + bool AnyUsed() const; 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/src/random.cpp b/src/random.cpp index b1c94a5..bf9fbee 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -26,11 +26,11 @@ #include "random.h" -static unsigned long state = 19670512; +static uint_least32_t state = 19670512; -static unsigned long modulus = BEEBASM_RAND_MODULUS; +static uint_least32_t modulus = BEEBASM_RAND_MODULUS; -void beebasm_srand(unsigned long seed) +void beebasm_srand(uint_least32_t seed) { state = seed % modulus; if ( state == 0 ) @@ -47,9 +47,9 @@ void beebasm_srand(unsigned long seed) } } -unsigned long 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 3d45bb6..c083289 100644 --- a/src/random.h +++ b/src/random.h @@ -24,12 +24,14 @@ #ifndef RANDOM_H_ #define RANDOM_H_ -#define BEEBASM_RAND_MULTIPLIER (48271UL) -#define BEEBASM_RAND_MODULUS (2147483647UL) -#define BEEBASM_RAND_MAX (BEEBASM_RAND_MODULUS - 2UL) +#include <stdint.h> -void beebasm_srand(unsigned long 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 long beebasm_rand(); +void beebasm_srand(uint_least32_t seed); + +uint_least32_t beebasm_rand(); #endif // RANDOM_H_ diff --git a/src/sourcecode.cpp b/src/sourcecode.cpp index 6965a21..aef44e0 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 ) { } @@ -215,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++; } @@ -247,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++; } @@ -280,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 @@ -287,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; } @@ -325,6 +333,7 @@ void SourceCode::CloseBrace( const string& line, int column ) throw AsmException_SyntaxError_MismatchedBraces( line, column ); } + SymbolTable::Instance().PopScope(); m_forStackPtr--; } @@ -562,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 dc12562..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 @@ -35,7 +37,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 +48,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; @@ -119,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 ); @@ -138,6 +143,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..401007a 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! @@ -93,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/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(); diff --git a/src/stringutils.cpp b/src/stringutils.cpp index 58b9174..5ad19f2 100644 --- a/src/stringutils.cpp +++ b/src/stringutils.cpp @@ -21,6 +21,8 @@ /*************************************************************************************************/ #include <iostream> +#include <sstream> +#include "globaldata.h" #include "stringutils.h" using namespace std; @@ -91,4 +93,66 @@ bool EatWhitespace( const string& line, size_t& column ) +/*************************************************************************************************/ +/** + FormattedErrorLocation() + + 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(); +} + + +/*************************************************************************************************/ +/** + 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) +{ + if ((-4294967296.0 < value) && (value < -0.5)) + { + unsigned int abs_part = static_cast<unsigned int>(-value); + if (-static_cast<double>(abs_part) == value) + { + dest << '-' << abs_part; + return; + } + } + else if ((0.0 <= value) && (value < 4294967296.0)) + { + 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/src/stringutils.h b/src/stringutils.h index 30ffa57..263148d 100644 --- a/src/stringutils.h +++ b/src/stringutils.h @@ -30,6 +30,8 @@ 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); } diff --git a/src/symboltable.cpp b/src/symboltable.cpp index 61b8324..d7d903b 100644 --- a/src/symboltable.cpp +++ b/src/symboltable.cpp @@ -22,11 +22,16 @@ #include <cmath> #include <cstdio> +#include <fstream> #include <iostream> #include <sstream> +#include "globaldata.h" +#include "objectcode.h" #include "symboltable.h" #include "constants.h" +#include "asmexception.h" +#include "literals.h" using namespace std; @@ -76,6 +81,7 @@ void SymbolTable::Destroy() */ /*************************************************************************************************/ SymbolTable::SymbolTable() + : m_labelScopes( 0 ) { // Add any constant symbols here @@ -124,10 +130,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 ) ) ); @@ -178,87 +184,75 @@ 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" ) ) - { - readHex = true; - valueString = valueString.substr( 2 ); - } - else if ( !valueString.compare( 0, 1, "%" ) ) + // Convert C-style hex prefix to beeb-style + if ( !valueString.compare( 0, 2, "0x" ) || !valueString.compare( 0, 2, "0X" ) ) { - readBinary = true; valueString = valueString.substr( 1 ); + valueString[0] = '&'; } - std::istringstream valueStream( valueString ); + size_t index = 0; double value; - char c; - - valueStream >> noskipws; - - if ( readHex ) + try { - int intValue; - - if ( ! ( valueStream >> hex >> intValue ) ) + if ( !Literals::ParseNumeric(valueString, index, value) ) { return false; } - - value = intValue; } - else if ( readBinary ) + catch (AsmException_SyntaxError const&) { - unsigned int intValue = 0; + return false; + } - int charOrEof = valueStream.get(); + if (index != valueString.length()) + { + return false; + } - if ( charOrEof == EOF ) - { - return false; - } + m_map.insert( make_pair( symbol, Symbol( value, false ) ) ); - while ( ( charOrEof == '0' ) || ( charOrEof == '1' ) ) - { - if ( intValue & 0x80000000 ) - { - return false; - } - intValue = 2 * intValue + (charOrEof - '0'); - charOrEof = valueStream.get(); - } + return true; +} - if ( charOrEof != EOF ) - { - return false; - } - value = static_cast<double>(intValue); - } - else if ( ! ( valueStream >> value ) ) + +/*************************************************************************************************/ +/** + 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; } - if ( valueStream.get( c ) ) + 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() @@ -268,7 +262,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(); @@ -318,30 +312,106 @@ 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 { - cout << "[{"; + std::ofstream labels; + std::ostream & our_cout = (labels_file && (labels.open(labels_file), !labels.bad())) ? labels : std::cout; + + our_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 ( symbol.IsLabel() && + symbolName.find_first_of( '@' ) == string::npos ) + { + // This doesn't output string valued symbols + Value value = symbol.GetValue(); + if (value.GetType() == Value::NumberValue) + { + if ( !bFirst ) + { + our_cout << ","; + } + + our_cout << "'" << symbolName << "':" << value.GetNumber() << "L"; + + bFirst = false; + } + } + } + } + + if (all) + { + for ( std::vector<Label>::const_iterator it = m_labelList.begin(); it != m_labelList.end(); ++it ) { if ( !bFirst ) { - cout << ","; + our_cout << ","; } - cout << "'" << symbolName << "':" << symbol.GetValue() << "L"; + our_cout << "'" << it->m_identifier << "':" << it->m_addr << "L"; bFirst = false; } } - cout << "}]" << endl; + our_cout << "}]" << endl; +} + +void SymbolTable::PushBrace() +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + if (m_lastLabel.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; + } + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); + } +} + +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; + m_lastLabel.m_identifier += label.str(); + m_lastLabel.m_addr = addr; + m_lastLabel.m_scope = m_labelScopes++; + m_labelStack.push_back(m_lastLabel); + } +} + +void SymbolTable::AddLabel(const std::string& symbol) +{ + if (GlobalData::Instance().IsSecondPass()) + { + int addr = ObjectCode::Instance().GetPC(); + m_lastLabel.m_identifier = (m_labelStack.empty() ? "" : m_labelStack.back().m_identifier) + "." + symbol; + m_lastLabel.m_addr = addr; + m_labelList.push_back(m_lastLabel); + } +} + +void SymbolTable::PopScope() +{ + if (GlobalData::Instance().IsSecondPass()) + { + m_labelStack.pop_back(); + m_lastLabel = m_labelStack.empty() ? Label() : m_labelStack.back(); + } } diff --git a/src/symboltable.h b/src/symboltable.h index 0a71f8c..d1e7f43 100644 --- a/src/symboltable.h +++ b/src/symboltable.h @@ -27,7 +27,9 @@ #include <cstdlib> #include <map> #include <string> +#include <vector> +#include "value.h" class SymbolTable { @@ -37,15 +39,20 @@ 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 ); + bool AddCommandLineStringSymbol( 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 ); - 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: @@ -53,15 +60,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; }; @@ -71,6 +78,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) {} + } m_lastLabel; + std::vector<Label> m_labelStack; + std::vector<Label> m_labelList; }; diff --git a/src/value.h b/src/value.h new file mode 100644 index 0000000..ff853e2 --- /dev/null +++ b/src/value.h @@ -0,0 +1,432 @@ +/*************************************************************************************************/ +/** + 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> +#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. +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); + 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); + if (header) + { + char* buffer = StringBuffer(header); + memcpy(buffer, StringData(header1), header1->m_length); + memcpy(buffer + header1->m_length, StringData(header2), header2->m_length); + } + 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); + if (header) + { + char* buffer = StringBuffer(header); + for (unsigned int i = 0; i != count; ++i) + { + memcpy(buffer, sourceData, sourceLength); + buffer += sourceLength; + } + } + 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) + { + return reinterpret_cast<char*>(header) + sizeof(StringHeader); + } + + static StringHeader* Allocate(unsigned int length) + { + int fullLength = sizeof(StringHeader) + length + 1; + 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; + } + + static StringHeader* Copy(StringHeader* source) + { + return Allocate(StringData(source), Length(source)); + } + + static int Compare(unsigned int a, unsigned int b) + { + if (a == b) + return 0; + else if (a < b) + return -1; + 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. +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); + } + 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); + } + 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); + } + 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) + { + if (!header) + { + throw std::bad_alloc(); + } + 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_ 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/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/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/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/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/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) 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 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) 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 new file mode 100644 index 0000000..7cb8a5a --- /dev/null +++ b/test/1-values/values.6502 @@ -0,0 +1,31 @@ +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) +assert(&abcdef=11259375) +assert(&7fffffff=2147483647) +\ Unlike BBC BASIC, hex constants are always positive +assert(&ffffffff=4294967295) +\ And so are binary constants now +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/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/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) 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) diff --git a/test/2-expressions/operators.6502 b/test/2-expressions/operators.6502 new file mode 100644 index 0000000..908d34c --- /dev/null +++ b/test/2-expressions/operators.6502 @@ -0,0 +1,90 @@ +\ 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) +assert(96 >> 5=3) +assert(3 >> -5=96) + +assert(-1>>5=-1) +assert(&80000000>>5=-&4000000) + +\ 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) + +\ 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) +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/2-expressions/stringfunctions.6502 b/test/2-expressions/stringfunctions.6502 new file mode 100644 index 0000000..e79ed65 --- /dev/null +++ b/test/2-expressions/stringfunctions.6502 @@ -0,0 +1,28 @@ +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 STR$(&FFFFFFFF) = "4294967295" +ASSERT STR$(-&FFFFFFFF) = "-4294967295" +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/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/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/autolinenumdemo.6502 b/test/3-directives/autolinenumdemo.6502 new file mode 100644 index 0000000..881a32c --- /dev/null +++ b/test/3-directives/autolinenumdemo.6502 @@ -0,0 +1,7 @@ +org &2000 +.start + rts +.end + +save "test", start, end +putbasic "autolinenumdemo.bas", "alndemo" diff --git a/test/3-directives/autolinenumdemo.bas b/test/3-directives/autolinenumdemo.bas new file mode 100644 index 0000000..9efd514 --- /dev/null +++ b/test/3-directives/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/3-directives/autolinenumdemo.gold.ssd b/test/3-directives/autolinenumdemo.gold.ssd new file mode 100644 index 0000000..785c8d8 Binary files /dev/null and b/test/3-directives/autolinenumdemo.gold.ssd differ diff --git a/test/3-directives/basicrhstoken.6502 b/test/3-directives/basicrhstoken.6502 new file mode 100644 index 0000000..e46a8c2 --- /dev/null +++ b/test/3-directives/basicrhstoken.6502 @@ -0,0 +1 @@ +putbasic "basicrhstoken.bas", "RHSTOK" diff --git a/test/3-directives/basicrhstoken.bas b/test/3-directives/basicrhstoken.bas new file mode 100644 index 0000000..f2d9a47 --- /dev/null +++ b/test/3-directives/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/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 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 0000000..2daaab8 Binary files /dev/null and b/test/3-directives/clear/clear.gold.ssd differ 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 , 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 0000000..6d6152c Binary files /dev/null and b/test/3-directives/copyblock/copyblock.gold.ssd differ 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 0000000..4e45037 Binary files /dev/null and b/test/3-directives/copyblock/copyblockjustfits.gold.ssd differ diff --git a/test/3-directives/copyblock/copyblockreuse.6502 b/test/3-directives/copyblock/copyblockreuse.6502 new file mode 100644 index 0000000..4bff157 --- /dev/null +++ b/test/3-directives/copyblock/copyblockreuse.6502 @@ -0,0 +1,25 @@ +\ COPYBLOCK - reuse memory from source of copy using CLEAR + +ORG &2000 + +.start + +LDY #&00 +.loop +LDA (&70),Y +STA (&72),Y +DEY +BNE loop +.loopend + +SKIP loopend-start +COPYBLOCK start,loopend,loopend +CLEAR start, start+2 + +.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 0000000..61525cc Binary files /dev/null and b/test/3-directives/copyblock/copyblockreuse.gold.ssd differ 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 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 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 0000000..ff2aaaf Binary files /dev/null and b/test/3-directives/incbin/incbin.gold.ssd differ 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 0000000..e868ece Binary files /dev/null and b/test/3-directives/incbin/incbinjustfits.gold.ssd differ 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 diff --git a/examples/invalidbasic1.bas b/test/3-directives/invalidbasic1.bas similarity index 100% rename from examples/invalidbasic1.bas rename to test/3-directives/invalidbasic1.bas diff --git a/examples/invalidbasic1.6502 b/test/3-directives/invalidbasic1.fail.6502 similarity index 78% rename from examples/invalidbasic1.6502 rename to test/3-directives/invalidbasic1.fail.6502 index 74738f2..4764ff4 100644 --- a/examples/invalidbasic1.6502 +++ b/test/3-directives/invalidbasic1.fail.6502 @@ -1,3 +1,5 @@ +\ beebasm -do test.ssd + org &2000 .start rts diff --git a/examples/invalidbasic2.bas b/test/3-directives/invalidbasic2.bas similarity index 100% rename from examples/invalidbasic2.bas rename to test/3-directives/invalidbasic2.bas diff --git a/examples/invalidbasic2.6502 b/test/3-directives/invalidbasic2.fail.6502 similarity index 78% rename from examples/invalidbasic2.6502 rename to test/3-directives/invalidbasic2.fail.6502 index b46d583..bc8b095 100644 --- a/examples/invalidbasic2.6502 +++ b/test/3-directives/invalidbasic2.fail.6502 @@ -1,3 +1,5 @@ +\ beebasm -do test.ssd + org &2000 .start rts 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 0000000..e36c633 Binary files /dev/null and b/test/3-directives/mapchar/mapchar.gold.ssd differ 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' + diff --git a/examples/putbasicnonexistentdemo.6502 b/test/3-directives/putbasicnonexistentdemo.fail.6502 similarity index 77% rename from examples/putbasicnonexistentdemo.6502 rename to test/3-directives/putbasicnonexistentdemo.fail.6502 index 790fce6..5c168e4 100644 --- a/examples/putbasicnonexistentdemo.6502 +++ b/test/3-directives/putbasicnonexistentdemo.fail.6502 @@ -1,3 +1,5 @@ +\ beebasm -do test.ssd + org &2000 .start rts 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..643439d --- /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 0000000..08c231e Binary files /dev/null and b/test/3-directives/putfiletext/putfile.gold.ssd differ diff --git a/examples/putfilenonexistentdemo.6502 b/test/3-directives/putfiletext/putfilenonexistentdemo.fail.6502 similarity index 100% rename from examples/putfilenonexistentdemo.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..e483e1a --- /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 0000000..1eb4983 Binary files /dev/null and b/test/3-directives/putfiletext/puttext.gold.ssd differ 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 0000000..0377c76 Binary files /dev/null and b/test/3-directives/putfiletext/putundef.gold.ssd differ 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. 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 0000000..a65a7c7 Binary files /dev/null and b/test/3-directives/save/savename.gold.ssd differ 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 0000000..f175f0d Binary files /dev/null and b/test/3-directives/save/savenoname1.gold.ssd differ 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 0000000..dc3c687 Binary files /dev/null and b/test/3-directives/save/savenoname2.gold.ssd differ 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 0000000..c852bf1 Binary files /dev/null and b/test/3-directives/save/savenoname3.gold.ssd differ 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" + 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 0000000..5f4e033 Binary files /dev/null and b/test/3-directives/save/saveundefexec.gold.ssd differ 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 0000000..d0c1d6f Binary files /dev/null and b/test/3-directives/skip/skip.gold.ssd differ 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, 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 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 0000000..d0c1d6f Binary files /dev/null and b/test/3-directives/skipto/skipto.gold.ssd differ 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 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 0000000..9f4d83d Binary files /dev/null and b/test/4-assembler/all6502.gold.ssd differ diff --git a/test/4-assembler/all65C02.6502 b/test/4-assembler/all65C02.6502 new file mode 100644 index 0000000..cd96367 --- /dev/null +++ b/test/4-assembler/all65C02.6502 @@ -0,0 +1,372 @@ + +\ Every valid 65C02 opcode. + +\ This is not thoroughly tested, 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 0000000..f9d5dca Binary files /dev/null and b/test/4-assembler/all65C02.gold.ssd differ 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 0000000..0057871 Binary files /dev/null and b/test/4-assembler/expressionrecovery.gold.ssd differ diff --git a/examples/local-forward-branch-1.6502 b/test/4-assembler/local-forward-branch-1.6502 similarity index 100% rename from examples/local-forward-branch-1.6502 rename to test/4-assembler/local-forward-branch-1.6502 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 0000000..b2ebcd9 Binary files /dev/null and b/test/4-assembler/local-forward-branch-1.gold.ssd differ diff --git a/examples/local-forward-branch-2.6502 b/test/4-assembler/local-forward-branch-2.6502 similarity index 100% rename from examples/local-forward-branch-2.6502 rename to test/4-assembler/local-forward-branch-2.6502 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 0000000..b2ebcd9 Binary files /dev/null and b/test/4-assembler/local-forward-branch-2.gold.ssd differ diff --git a/test/4-assembler/local-forward-branch-3.6502 b/test/4-assembler/local-forward-branch-3.6502 new file mode 100644 index 0000000..1aaa2b2 --- /dev/null +++ b/test/4-assembler/local-forward-branch-3.6502 @@ -0,0 +1,20 @@ +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 + lda zp +endmacro + +.start + + foo a + +.end + +save "test", start, end 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 0000000..d94a8e4 Binary files /dev/null and b/test/4-assembler/local-forward-branch-3.gold.ssd differ diff --git a/examples/local-forward-branch-4.6502 b/test/4-assembler/local-forward-branch-4.6502 similarity index 100% rename from examples/local-forward-branch-4.6502 rename to test/4-assembler/local-forward-branch-4.6502 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 0000000..3b24cd7 Binary files /dev/null and b/test/4-assembler/local-forward-branch-4.gold.ssd differ diff --git a/examples/local-forward-branch-5.6502 b/test/4-assembler/local-forward-branch-5.6502 similarity index 100% rename from examples/local-forward-branch-5.6502 rename to test/4-assembler/local-forward-branch-5.6502 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 diff --git a/test/4-assembler/scopejumpdemo1.6502 b/test/4-assembler/scopejumpdemo1.6502 new file mode 100644 index 0000000..49223ef --- /dev/null +++ b/test/4-assembler/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/4-assembler/scopejumpdemo1.gold.ssd b/test/4-assembler/scopejumpdemo1.gold.ssd new file mode 100644 index 0000000..f4e9c1f Binary files /dev/null and b/test/4-assembler/scopejumpdemo1.gold.ssd differ diff --git a/test/4-assembler/scopejumpdemo2.6502 b/test/4-assembler/scopejumpdemo2.6502 new file mode 100644 index 0000000..c9b3f0f --- /dev/null +++ b/test/4-assembler/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/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 0000000..5669df2 Binary files /dev/null and b/test/4-assembler/scopejumpdemo2.gold.ssd differ diff --git a/test/4-assembler/scopejumpdemo2.inc.6502 b/test/4-assembler/scopejumpdemo2.inc.6502 new file mode 100644 index 0000000..e07789e --- /dev/null +++ b/test/4-assembler/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/5-errors/assertdemo.fail.6502 b/test/5-errors/assertdemo.fail.6502 new file mode 100644 index 0000000..6ac5f66 --- /dev/null +++ b/test/5-errors/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/5-errors/callstackdemo.fail.6502 b/test/5-errors/callstackdemo.fail.6502 new file mode 100644 index 0000000..3479313 --- /dev/null +++ b/test/5-errors/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/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 diff --git a/test/5-errors/errorlinenumber1.fail.6502 b/test/5-errors/errorlinenumber1.fail.6502 new file mode 100644 index 0000000..f7d1382 --- /dev/null +++ b/test/5-errors/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/5-errors/errorlinenumber1.fail.gold.txt b/test/5-errors/errorlinenumber1.fail.gold.txt new file mode 100644 index 0000000..5f9dcc8 --- /dev/null +++ b/test/5-errors/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/5-errors/errorlinenumber2.fail.6502 b/test/5-errors/errorlinenumber2.fail.6502 new file mode 100644 index 0000000..b585b14 --- /dev/null +++ b/test/5-errors/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/5-errors/errorlinenumber2.fail.gold.txt b/test/5-errors/errorlinenumber2.fail.gold.txt new file mode 100644 index 0000000..f60f45c --- /dev/null +++ b/test/5-errors/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/5-errors/filelinecallstackdemo.6502 b/test/5-errors/filelinecallstackdemo.6502 new file mode 100644 index 0000000..c89c543 --- /dev/null +++ b/test/5-errors/filelinecallstackdemo.6502 @@ -0,0 +1,29 @@ +\ beebasm -q + +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/5-errors/filelinecallstackdemo.gold.ssd b/test/5-errors/filelinecallstackdemo.gold.ssd new file mode 100644 index 0000000..ab96c56 Binary files /dev/null and b/test/5-errors/filelinecallstackdemo.gold.ssd differ diff --git a/test/5-errors/filelinecallstackdemo.gold.txt b/test/5-errors/filelinecallstackdemo.gold.txt new file mode 100644 index 0000000..212402c --- /dev/null +++ b/test/5-errors/filelinecallstackdemo.gold.txt @@ -0,0 +1,4 @@ +Current location:filelinecallstackdemo.6502:16 +Current call stack:filelinecallstackdemo.6502:11 +filelinecallstackdemo.6502:6 +filelinecallstackdemo.6502:19 (end) 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 diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..cb92e07 --- /dev/null +++ b/test/README.md @@ -0,0 +1,39 @@ +# 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 `sometest.6502` and `sometest.gold.ssd` then +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. + diff --git a/test/testrunner.py b/test/testrunner.py new file mode 100644 index 0000000..8ec3ef9 --- /dev/null +++ b/test/testrunner.py @@ -0,0 +1,202 @@ +# ===================================================================================================== +# +# 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, '-v'] + read_beebasm_switches(file_name) + if ssd_name != None: + args += ['-do', ssd_name] + args += ['-i', file_name] + return args + +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() + + 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'): + 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') + 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) + + 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: + 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): + # 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) + +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' +else: + beebasm = 'beebasm' +beebasm = os.path.join(os.getcwd(), 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: beebasm tests succeeded", file = original_stdout) + sys.exit(0) + +except TestFailure as e: + print("FAILURE: " + e.args[0], file = original_stdout) + sys.exit(1) +