From 8219b4d8f90467d42f8ecd2b13a93533d44fa0b8 Mon Sep 17 00:00:00 2001 From: Michael Kamprath Date: Fri, 26 Apr 2024 22:56:31 -0700 Subject: [PATCH] Minimal 64x4 Support (#31) This is a rather large change that enables several assembly syntax features to support the Minimal 64x4 Home Computer. The major changes are: * address operand type that support bit slicing when needed for things like local jumps and zero page addressing. * .align directive to adjust current address to next configurable page boundary * Support for embedded strings, which which are essentially .cstr data types without the .cstr directive. * #mute and #unmute preprocessor directive to control the emission of byte code. * Complete list of changes is in the CHANGELOG. Also created the ISA configuration and sample code for the Minimal 64x4 Home Computer. --- .pre-commit-config.yaml | 8 +- .vscode/launch.json | 13 + CHANGELOG.md | 9 + Pipfile.lock | 301 +- docs/named-memory-zones-requirements.md | 14 +- examples/ben-eater-sap1/README.md | 1 - examples/ben-eater-sap1/multiplication.sap1 | 1 - examples/kenbak-1/led-bouncer.kb1 | 12 +- examples/kenbak-1/led-chaser.kb1 | 2 +- examples/slu4-minimal-64x4/README.md | 72 + .../slu4-minimal-64x4/slu4-minimal-64x4.yaml | 3031 +++++++++++++++++ .../software/hello-world.min64x4 | 10 + .../software/mathlib32.min64x4 | 263 ++ .../slu4-minimal-64x4/software/primes.min64x4 | 128 + .../software/random-maze.min64x4 | 31 + .../slu4-minimal-64x4/software/stars.min64x4 | 124 + .../software/stringlib.min64x4 | 193 ++ .../slu4-minimal-64x4/software/vga.min64x4 | 156 + examples/slu4-minimal-cpu/mathlib.min-asm | 16 +- examples/slu4-minimal-cpu/random-maze.min-asm | 10 +- .../slu4-minimal-cpu/random_stars.min-asm | 2 +- pyproject.toml | 2 +- src/bespokeasm/__init__.py | 4 +- src/bespokeasm/__main__.py | 28 +- src/bespokeasm/assembler/assembly_file.py | 5 +- .../assembler/bytecode/assembled.py | 4 +- .../assembler/bytecode/generator/__init__.py | 3 +- .../assembler/bytecode/generator/macro.py | 5 +- .../assembler/bytecode/packed_bits.py | 2 +- src/bespokeasm/assembler/engine.py | 33 +- src/bespokeasm/assembler/keywords.py | 15 +- src/bespokeasm/assembler/line_identifier.py | 1 - .../assembler/line_object/__init__.py | 22 +- .../assembler/line_object/data_line.py | 2 +- .../assembler/line_object/directive_line.py | 267 -- .../line_object/directive_line/__init__.py | 0 .../line_object/directive_line/address.py | 48 + .../line_object/directive_line/factory.py | 134 + .../line_object/directive_line/fill_data.py | 78 + .../line_object/directive_line/memzone.py | 35 + .../line_object/directive_line/page_align.py | 60 + .../assembler/line_object/emdedded_string.py | 62 + .../assembler/line_object/factory.py | 25 +- .../assembler/line_object/instruction_line.py | 10 +- .../assembler/line_object/label_line.py | 2 +- .../line_object/preprocessor_line/__init__.py | 2 +- .../preprocessor_line/condition_line.py | 26 +- .../preprocessor_line/create_memzone.py | 4 +- .../preprocessor_line/define_symbol.py | 4 +- .../line_object/preprocessor_line/factory.py | 1 + .../preprocessor_line/required_language.py | 8 +- src/bespokeasm/assembler/model/__init__.py | 19 +- src/bespokeasm/assembler/model/instruction.py | 2 +- .../assembler/model/instruction_macro.py | 2 +- .../assembler/model/instruction_parser.py | 4 +- .../assembler/model/operand/__init__.py | 9 +- .../assembler/model/operand/factory.py | 4 +- .../assembler/model/operand/types/address.py | 134 + .../model/operand/types/deferred_numeric.py | 2 +- .../operand/types/enumeration_operand.py | 2 +- .../model/operand/types/indexed_register.py | 2 +- .../types/indirect_indexed_register.py | 4 +- .../model/operand/types/indirect_numeric.py | 2 +- .../model/operand/types/indirect_register.py | 8 +- .../model/operand/types/numeric_bytecode.py | 2 +- .../assembler/model/operand/types/register.py | 8 +- .../model/operand/types/relative_address.py | 6 +- .../assembler/model/operand_parser.py | 5 +- .../assembler/preprocessor/__init__.py | 2 +- .../assembler/preprocessor/condition.py | 79 +- .../assembler/preprocessor/condition_stack.py | 25 +- .../assembler/preprocessor/symbol.py | 4 +- .../assembler/pretty_printer/intelhex.py | 4 +- .../assembler/pretty_printer/listing.py | 17 +- .../assembler/pretty_printer/minhex.py | 4 +- .../pretty_printer/source_details.py | 4 +- src/bespokeasm/configgen/sublime/__init__.py | 6 +- .../resources/sublime-color-scheme.json | 2 +- .../sublime/resources/sublime-syntax.yaml | 2 + src/bespokeasm/configgen/vscode/__init__.py | 16 +- .../configgen/vscode/resources/package.json | 2 +- .../configgen/vscode/resources/tmGrammar.json | 4 +- .../configgen/vscode/resources/tmTheme.xml | 6 +- src/bespokeasm/expression/__init__.py | 2 +- src/bespokeasm/utilities.py | 2 +- test/__init__.py | 2 +- .../test_instructions_with_periods.yaml | 125 + .../test_valid_address_enforcement.yaml | 52 +- test/test_assembler_model.py | 10 +- test/test_bytecode.py | 2 +- test/test_configgen.py | 30 +- test/test_directive_lines.py | 96 +- test/test_expression.py | 22 +- test/test_instruction_parsing.py | 160 +- test/test_line_objects.py | 106 +- test/test_preprocessor_symbols.py | 75 +- test/test_utilities.py | 2 +- 97 files changed, 5714 insertions(+), 653 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 examples/slu4-minimal-64x4/README.md create mode 100644 examples/slu4-minimal-64x4/slu4-minimal-64x4.yaml create mode 100644 examples/slu4-minimal-64x4/software/hello-world.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/mathlib32.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/primes.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/random-maze.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/stars.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/stringlib.min64x4 create mode 100644 examples/slu4-minimal-64x4/software/vga.min64x4 delete mode 100644 src/bespokeasm/assembler/line_object/directive_line.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/__init__.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/address.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/factory.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/fill_data.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/memzone.py create mode 100644 src/bespokeasm/assembler/line_object/directive_line/page_align.py create mode 100644 src/bespokeasm/assembler/line_object/emdedded_string.py create mode 100644 src/bespokeasm/assembler/model/operand/types/address.py create mode 100644 test/config_files/test_instructions_with_periods.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac4ab2c..d6addd7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,22 +2,26 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-json - id: check-toml + - id: check-xml - id: check-added-large-files args: ['--maxkb=10000'] - id: mixed-line-ending + - id: check-docstring-first + - id: double-quote-string-fixer - repo: https://github.com/pycqa/flake8 rev: '6.0.0' hooks: - id: flake8 args: [--max-line-length=127] - repo: https://github.com/asottile/pyupgrade - rev: v3.6.0 + rev: v3.15.2 hooks: - id: pyupgrade + args: [--py39-plus] diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9197dd3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a5374b0..a49a0fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,17 @@ Changes that are planned but not implemented yet: * Disallowed operands * missing `:` after labels * unknown labels +* Add named label scopes. This would allow a label to be defined in a specific scope that can be shared across files. +* Create a "align if needed" preprocessor directive paid that generates an `.align` directive is the bytecode in between the paid isn't naturally on the same page and can fit on the same page if aligned. An error would be benerated if the block of code can't fit on the same page regardless of alignment. ## [Unreleased] +* Added support for The Minimal 64x4 Home Computer with an example and updated assembler functionality to support it. +* Added `address` operand type that enables several features specific to absolute addresses, include slicing the address to support "short jump" type instructions. +* Added `.align` directive to align the current address to a multiple of a given value. +* Changed syntax highlight color theme name to be specific to the language rather than the generic "BespokeASM Theme" name. +* Added optional support for embedded strings in the assembly code. When enabled, strings can be ebdedded in the code withou a data directive such as `.cstr`. This is enabled by setting the `allow_embedded_strings` option in the `general` section of the configuration file to `true`. +* Added ability to mute byte code emission with the preprocessor directive `#mute` and unmute with `#unmute`. +* Improved handling of include directories by duplicating and normalizing all search paths. ## [0.4.1] * added `.asciiz` as an equivalent data directive to `.cstr` diff --git a/Pipfile.lock b/Pipfile.lock index 733fc2f..28ae052 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,19 +18,21 @@ "default": { "build": { "hashes": [ - "sha256:1a07724e891cbd898923145eb7752ee7653674c511378eb9c7691aab1612bc3c", - "sha256:38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69" + "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d", + "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4" ], "index": "pypi", - "version": "==0.9.0" + "markers": "python_version >= '3.8'", + "version": "==1.2.1" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "index": "pypi", - "version": "==8.1.3" + "markers": "python_version >= '3.7'", + "version": "==8.1.7" }, "intelhex": { "hashes": [ @@ -42,200 +44,209 @@ }, "packaging": { "hashes": [ - "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", - "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" ], "markers": "python_version >= '3.7'", - "version": "==22.0" + "version": "==24.0" }, - "pep517": { + "pyproject-hooks": { "hashes": [ - "sha256:4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b", - "sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59" + "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8", + "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5" ], - "markers": "python_version >= '3.6'", - "version": "==0.13.0" + "markers": "python_version >= '3.7'", + "version": "==1.0.0" }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "index": "pypi", - "version": "==6.0" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" + "markers": "python_version >= '3.6'", + "version": "==6.0.1" } }, "develop": { "cfgv": { "hashes": [ - "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", - "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.3.1" + "markers": "python_version >= '3.8'", + "version": "==3.4.0" }, "distlib": { "hashes": [ - "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", - "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", + "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64" ], - "version": "==0.3.6" + "version": "==0.3.8" }, "filelock": { "hashes": [ - "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2", - "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c" + "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f", + "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4" ], - "markers": "python_version >= '3.7'", - "version": "==3.8.2" + "markers": "python_version >= '3.8'", + "version": "==3.13.4" }, "identify": { "hashes": [ - "sha256:dce9e31fee7dbc45fea36a9e855c316b8fbf807e65a862f160840bb5a2bf5dfd", - "sha256:fb7c2feaeca6976a3ffa31ec3236a6911fbc51aec9acc111de2aed99f244ade2" + "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791", + "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e" ], - "markers": "python_version >= '3.7'", - "version": "==2.5.10" + "markers": "python_version >= '3.8'", + "version": "==2.5.35" }, "nodeenv": { "hashes": [ - "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e", - "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b" + "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2", + "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.7.0" + "version": "==1.8.0" }, "platformdirs": { "hashes": [ - "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", - "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" ], - "markers": "python_version >= '3.7'", - "version": "==2.6.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.0" }, "pre-commit": { "hashes": [ - "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7", - "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959" + "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab", + "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060" ], "index": "pypi", - "version": "==2.20.0" + "markers": "python_version >= '3.9'", + "version": "==3.7.0" }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "index": "pypi", - "version": "==6.0" + "markers": "python_version >= '3.6'", + "version": "==6.0.1" }, "setuptools": { "hashes": [ - "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54", - "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75" - ], - "markers": "python_version >= '3.7'", - "version": "==65.6.3" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987", + "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" + "markers": "python_version >= '3.8'", + "version": "==69.5.1" }, "virtualenv": { "hashes": [ - "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4", - "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058" + "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a", + "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197" ], - "markers": "python_version >= '3.6'", - "version": "==20.17.1" + "markers": "python_version >= '3.7'", + "version": "==20.25.1" } } } diff --git a/docs/named-memory-zones-requirements.md b/docs/named-memory-zones-requirements.md index f5c931b..465fdb0 100644 --- a/docs/named-memory-zones-requirements.md +++ b/docs/named-memory-zones-requirements.md @@ -14,14 +14,14 @@ A named memory zone is a contiguous address range in the allowable address space * A named memory zone must be completely contained by the allowed memory space of the configured ISA. * Multiple named memory zones may overlap with each other * When byte code is assembled, multiple byte codes assigned to the same absolute memory address is a fatal error. -* Named memory zones are a compile time construct, and are intended to only be a means to manage memory ranges and byte code memory locations in assembly code. +* Named memory zones are a compile time construct, and are intended to only be a means to manage memory ranges and byte code memory locations in assembly code. * Memory zones have a start and end absolute memory address. Byte code assigned to that memory zone with an absolute address outside of the memory zone's range will be an error. * A memory zone's name cannot be also used for any label. #### Creation ##### Global Memory Zone -By default, a memory zone named `GLOBAL` is defined to be the full range of memory addresses allowed by the instruction set configuration file. For example, if the ISA defines a 16-bit address type, then the `GLOBAL` memory zone will be addresses `0x0000` though `0xFFFF`. +By default, a memory zone named `GLOBAL` is defined to be the full range of memory addresses allowed by the instruction set configuration file. For example, if the ISA defines a 16-bit address type, then the `GLOBAL` memory zone will be addresses `0x0000` though `0xFFFF`. The `GLOBAL` memory zone can be redefined in the ISA configuration to be a subset of what is permitted by the memory address bit size. @@ -34,7 +34,7 @@ A memory zone can be defined with the following directive Where `` is an alphanumeric string with no spaces which will serve as the memory zone name, `` is the absolute address of the start of the memory zone, and `` is the absolute address of the end of the memory zone. Both `` and `` must be defined with integer literals. -Any defined memory zone must be fully contained in the `GLOBAL` memory zone. +Any defined memory zone must be fully contained in the `GLOBAL` memory zone. ##### ISA Configuration A predefined memory zone can be defined in the instruction set configuration file. In the `predefined` section, a subsection named `memory_zones` can be defined. That second contains a list of dictionaries with the following keys: @@ -56,7 +56,7 @@ By default, code in any given source file is assembled into the `GLOBAL` memory .memzone ``` -Note that the `GLOBAL` memory zone name can be used this directive. Subsequent assembly code lines will be compiled into the indicated memory zone scope until the end of the current assembly file or another directive that changes the memory zone scope. Addresses assigned to the byte code will be per the code ordering. +Note that the `GLOBAL` memory zone name can be used this directive. Subsequent assembly code lines will be compiled into the indicated memory zone scope until the end of the current assembly file or another directive that changes the memory zone scope. Addresses assigned to the byte code will be per the code ordering. Non-contiguous uses of a given memory zone scope will be compiled as if the assembly code in each use instance was concatenated together in the order processed by the assembler. @@ -75,7 +75,7 @@ Where `
` is the positive offset from the start of the spec .org 0x0100 "variables" ``` -Would be the same as setting the current origin to `0x2100` in the `GLOBAL` scope. +Would be the same as setting the current origin to `0x2100` in the `GLOBAL` scope. Not specifying a `` will cause the `
` to be interpreted as an absolute address. So: @@ -85,7 +85,7 @@ Not specifying a `` will cause the `
` to Will set the current address to $3400. This absolute address interpretation is regardless of how the `GLOBAL` memory zone is defined. -When using `GLOBAL` as the `` then `
` will be interpreted as an offset form the start of the `GLOBAL` memory zone as it would with any other named memory zone. If the `GLOBAL` memory zone has not be redefined, the net effect is the same as using `.org` with an absolute address. However, if the start address of the `GLOBAL` memory zone has been redefined, then `
` will be applied as an offset from the redefined start of `GLOBAL`. +When using `GLOBAL` as the `` then `
` will be interpreted as an offset form the start of the `GLOBAL` memory zone as it would with any other named memory zone. If the `GLOBAL` memory zone has not be redefined, the net effect is the same as using `.org` with an absolute address. However, if the start address of the `GLOBAL` memory zone has been redefined, then `
` will be applied as an offset from the redefined start of `GLOBAL`. ### Memory Zone Error Conditions @@ -94,4 +94,4 @@ The following conditions will be considered an error: * A defined memory zone not fully contained by the `GLOBAL` memory zone. * A memory zone defined more than once. * Byte code that get assigned to the same absolute memory address. -* Memory zone names that have spaces or non-alphanumeric characters. \ No newline at end of file +* Memory zone names that have spaces or non-alphanumeric characters. diff --git a/examples/ben-eater-sap1/README.md b/examples/ben-eater-sap1/README.md index 6eae581..0853016 100644 --- a/examples/ben-eater-sap1/README.md +++ b/examples/ben-eater-sap1/README.md @@ -46,4 +46,3 @@ bespokeasm compile -p -c eater-sap1-isa.yaml my_code.sap1 ### Syntax Highlighting BespokeASM has the ability to generate for various popular text editors a syntax highlighting language extension specific to this Ben Easter SAP-1 instruction set. [See the documentation](https://github.com/michaelkamprath/bespokeasm/wiki/Installation-and-Usage#installing-language-extensions) for information for on how to install and use the language extensions. - diff --git a/examples/ben-eater-sap1/multiplication.sap1 b/examples/ben-eater-sap1/multiplication.sap1 index 634b6e6..c67b5a1 100644 --- a/examples/ben-eater-sap1/multiplication.sap1 +++ b/examples/ben-eater-sap1/multiplication.sap1 @@ -31,4 +31,3 @@ done: lda result ; Load final product value out ; Display product value hlt ; Halt - diff --git a/examples/kenbak-1/led-bouncer.kb1 b/examples/kenbak-1/led-bouncer.kb1 index 4dad101..11cb6fc 100644 --- a/examples/kenbak-1/led-bouncer.kb1 +++ b/examples/kenbak-1/led-bouncer.kb1 @@ -1,10 +1,10 @@ ; LED Chaser ; -; This program causes the LEDs on the KENBAK-1 display to "bounce" between the +; This program causes the LEDs on the KENBAK-1 display to "bounce" between the ; edges or between positions corresponding to pressed input buttons. The -; bouncing algorithm doesn't handle being too tightly contaied and so +; bouncing algorithm doesn't handle being too tightly contaied and so ; the boucing will "tunnel" through the barrier in those cases ;-) -; +; start: ld a, 1 @@ -68,7 +68,7 @@ wall_bounce: .bounce_right: ld a, %01000000 ; load A with new ball position ld [ball_position], a ; update ball position variable - jp main_loop + jp main_loop ; toggle_direction @@ -90,7 +90,7 @@ toggle_direction: ; update_display ; Updates the display register to have a combination of the ball poisiton ; and the buttons pressed based on PWM status. -; +; PWM_MASK = %00000111 update_display: .byte 0 ; return address storage @@ -113,4 +113,4 @@ loop_counter: ball_position: .byte 0 left_right: - .byte 0 \ No newline at end of file + .byte 0 diff --git a/examples/kenbak-1/led-chaser.kb1 b/examples/kenbak-1/led-chaser.kb1 index f069f6a..bce0ec4 100644 --- a/examples/kenbak-1/led-chaser.kb1 +++ b/examples/kenbak-1/led-chaser.kb1 @@ -57,4 +57,4 @@ delay: sub b, 1 ; subtract one from B jpnz b, .loop ; if B is not zero, continue with loop .end: - jp [[delay]] ; return to caller \ No newline at end of file + jp [[delay]] ; return to caller diff --git a/examples/slu4-minimal-64x4/README.md b/examples/slu4-minimal-64x4/README.md new file mode 100644 index 0000000..78e45f1 --- /dev/null +++ b/examples/slu4-minimal-64x4/README.md @@ -0,0 +1,72 @@ +# Minimal 64x4 Home Computer +*Current as of v1.1.x of the Minimal 64x4 Home Computer* + +The Minimal 64x4 Home Computer (Minimal 64) is TTL CPU designed by Carsten Herting (slu4). Carsten has made his [Minimal 64x4 design open and available to others](https://github.com/slu4coder/Minimal-64x4-Home-Computer) to build. The Minimal 64x4 is an improvedment on The Minimal 64 Home Computer. Carsten has done and amazing job [documenting the Minimal 64x4](https://docs.google.com/document/d/1-nDv_8WEG1FrlO3kEK0icoYo-Z-jlhpCMiCstxGOCjQ/edit?usp=sharing). + +## Minimal 64x4 Assembly +The **BespokeASM** instruction set configration file `slu4-minimal-64x4.yaml` is available in this directory. Assuming that **BespookeASM** is [properly installed in the current python environment](https://github.com/michaelkamprath/bespokeasm/wiki/Installation-and-Usage#installation), to compile Minimal 64x4 assembly into a Intel Hex representation that the Minimal 64x4 OS's `receive` instruction can take, use the following command: + +```sh +bespokeasm compile -n -p -t intel_hex -c /path/to/slu4-minimal-64x4.yaml /path/to/my-code.min64x4 +``` + +The arguments to the command above are: + +* `-n` - Indicates that no binary image should be generated. +* `-p` - indicates that a textual representation of the assembled code should be emitted. +* `-t intel_hex` - Specifies the format of the textual rerpesentation of the compiled code, in this case being Intel Hex. If you ommit this option, the default textual representation of an human-readable listing will be used. +* `-c /path/to/slu4-minimal-64x4.yaml` - The file path to the **BespokeASM** instruction set configuration for the Minimal 64x4. +* `/path/to/my-code.min64x4` - The file path to the Minimal 64x4 assembly code to be compiled. Here by convention the assembly code has a file extension of `.min64x4`. While **BespokeASM** can work with any file extension for the code, the convention is used so that code editors know what file type they are editing and thus are able to support syntax highlighting specific to the Minimal 64x4 assembly syntax. See [**BespokeASM**'s documentation on syntax highlighting support](https://github.com/michaelkamprath/bespokeasm/wiki/Installation-and-Usage#installing-language-extensions) for more information. + +Once compiled, the intel hex output can be copied and pasted into the Minimal 64x4 OS's terminal window to load the code into the Minimal 64x4's memory using it's `receive` command. Further, the compile code can be run on [the Minimal 64x4's web-based emulator](https://editor.p5js.org/slu4coder/sketches/lRq1gyYR2). + +### Instruction Set +Carsten Herting thoroughly documents [the instruction set for the Minimal 64x4 in his user guide](https://docs.google.com/document/d/1-nDv_8WEG1FrlO3kEK0icoYo-Z-jlhpCMiCstxGOCjQ/edit?usp=sharing). All of the documented instructions in their original syntax are implemented in in this **BespokeASM** port. However, **BespokeASM** will be case insensitive when matching instruction mnemonics. + +### Instruction Macros +The following instruction macros have been added in the ISA configuration file for the Minimal 64. All macros that interact with the stack maintain byte order according to the Minimal 64x4 OS calling convention, which pushes the LSB of the value first despite the system otherwise using little endian byte ordering. Not that this means multibyte values on the stack cannot be used directly art their stack memory address and must be "pulled" from the stack to another memory location where they can be represented in little endian byte ordering. + +| Macros Instruction | Operand 1 | Operand 2 | Description | +|:-:|:-:|:-:|:--| +| `spinit` | - | - | Init the stack popint to a value of `0xFFFE`. | +| `phsi` | immediate | - | Pushes a 1 byte immediate byte onto the stack. | +| `phs2i` | immediate | - | Pushes a 2 byte immediate word onto the stack. | +| `phs4i` | immediate | - | Pushes a 4 byte immediate long onto the stack. | +| `phsptr` | abs address | - | Pushes a 2 byte immediate absolute address onto the stack per the Min 64x4 calling convention. Similar to `phs2i` but the operand is validated as an address. | +| `phs2s` | offset | - | Pushes a 2 byte word from the stack at the given offset onto stack | +| `phs4s` | offset | - | Pushes a 4 byte long from the stack at the given offset onto stack | +| `phsz` | zero page address | - | Pushes a 1 byte value from the zero page address onto the stack. | +| `phsv` | zero page address | - | Pushes a 2 byte word from the zero page address onto the stack. | +| `phsq` | zero page address | - | Pushes a 4 byte long from the zero page address onto the stack. | +| `pls2` | - | - | Pops a 2 byte value from the stack. | +| `pls4` | - | - | Pops a 4 byte value from the stack. | +| `mws2` | abs address | offset | Copies a 2 byte word from an absolute address to a specific offset on the stack. | +| `ms2w` | offset | abs address | Copies a 2 byte word from a specific offset on the stack to an absolute address. | +| `mvs2` | zero page address | offset | Copies a 2 byte word from a zero page address to a specific offset on the stack. | +| `ms2v` | offset | zero page address | Copies a 2 byte word from a specific offset on the stack to a zero page address. | +| `ms4q` | offset | zero page address | Copies a 4 byte long from a specific offset on the stack to a zero page address. | +| `mqs4` | zero page address | offset | Copies a 4 byte long from a zero page address to a specific offset on the stack. | +| `mls4` | abs address | offset | Copies a 4 byte long from an absolute address to a specific offset on the stack. | +| `ms4l` | offset | abs address | Copies a 4 byte long from a specific offset on the stack to an absolute address. | +| `aqq` | zero page address | zero page address | Adds two 4 byte longs from zero page addresses and stores the result in the second zero page address. | +| `sqq` | zero page address | zero page address | Subtracts the first 4 byte long at a zero page address from the second and stores the result in the second zero page address. | +| `mqq` | zero page address | zero page address | Copies a 4 byte long at the first zero page address to the 4 bytes at the second zero page address | +| `mll` | abs address | abs address | Copies a 4 byte long at the first absolute address to the 4 bytes at the second absolute address | +| `miq` | immediate | zero page address | Copies an immediate 4-byte long to a zero page long | +| `inq_` | zero page address | - | Increments a 4 byte long at a zero page address. Fixes the bug with the built-in `INQ` instruction as of v1.1.0 of the Minimal 64x4 OS. | + +### Assembly Syntax +**BespokeASM**'s syntax is close to the syntax that Carsten used for the Minimal 64x4's assembly language. However, there are some differences: + +* **BespokeASM** used a different syntax for some Minimal 64x4 directives: + * The `#org` directive, which sets the next byte code address to a specific value, is replicated in **BespokeASM** with the `.org` directive. + * The `#page` directive, which aligns the next byte code address to a page boundarry, is replicated in **BespokeASM** with the `.align` directive. + * In general **BespokeASM** also has [a richer set of directives available](https://github.com/michaelkamprath/bespokeasm/wiki/Assembly-Language-Syntax#directives). +* The Minimal 64x4's assembler has no data type directives. Instead, the assembler directly converts numeric values and strings to byte code exactly where it sits in the code. In **BespokeASM** one must declare a data type using [a data directive](https://github.com/michaelkamprath/bespokeasm/wiki/Assembly-Language-Syntax#data) when defining data in the byte code. + * Highly related to this is that the Minimal 64x4 assembler allows the direct placement of bytes using an "embedded string". That is, a string of characters surrounded by quotes that are not part of an instruction or directive. **BespokeASM** allows something similar with it's [embedded string]() data type. The main difference is that **BespokeASM**'s embedded string feature behaves similar to it's `.cstr` data directive, which means it will automatically appended a terminating character. For the Minimal 64x4 ISA configation, the terminating character has been set to `0`. In the Minimal 64x4 assembler, the terminating character must be explicitly added, and if you review Carsten's code, you will see that he does this where embedded strings are used in conjuction with the `_Print` routine. +* The Minimal 64x4 assembler emits byte code in the order it was in the original assembly code, while **BespokeASM** emits byte code in address order. For the most part, these two orderings can be pretty much the same, however, differences will show up if multiple `.org` directives are use or multiple additional source files are included. +* The Minimal 64x4 uses a `<` and `>` to do byte slicing of constant values, with `<` meaning least significant byte and `>` meaning most significant byte (and it assumes a 2 byte value). **BespokeASM** using `BYTE0(..)` and `BYTE1(..)` [to accomplish the same goals](https://github.com/michaelkamprath/bespokeasm/wiki/Assembly-Language-Syntax#numeric-expressions) (respectively). + * It's worth noting that Minimal 64x4's assembler frequently uses the `<` operator to slice a zero page address to just it's LSB. That is, it converts the word `0x0080` to the byte `0x80`, removing the zero-valued MSB. The Minimal 64x4 ISA configuration for **BespokeASM** is set up to do this LSB byte slicing automatically for zero page addresses operands in instructions that expects a zero page address. For example, in Minimal 64x4 assembly, you might write `STZ <_Xpos` to store the `A` register value in the zero page address defined by the MinOS constant `_Xpos`, which has the value of `0x00C0`. In **BespokeASM** you can write `STZ _Xpos` to accomplish the same thing as the operand to `STZ` is configured to automatically slice the passed address value to just it's LSB, plus it will also ensure that the MSB of the operand value is zero producing an error if it is not thus ensuring you actually passed a zero page address. + + +There are several other features that **BespokeASM** provides over the Minimal 64x4 assembly syntax, such as richer math expressions for constant values and [several preprocessor directives](https://github.com/michaelkamprath/bespokeasm/wiki/Assembly-Language-Syntax#preprocessor), but the differences listed above are the ones that require code written for the Minimal 64 assembler to be altered to be assembled with **BespokeASM**. diff --git a/examples/slu4-minimal-64x4/slu4-minimal-64x4.yaml b/examples/slu4-minimal-64x4/slu4-minimal-64x4.yaml new file mode 100644 index 0000000..2ffc26b --- /dev/null +++ b/examples/slu4-minimal-64x4/slu4-minimal-64x4.yaml @@ -0,0 +1,3031 @@ +--- +description: slu4 Minimal 64x4 Home Computer +general: + address_size: 16 + page_size: 256 + endian: little + registers: + - a + - b + - bank + origin: 0x0000 + identifier: + name: slu4-min64x4-asm + version: "1.1.0" + extension: min64x4 + min_version: "0.4.2" + allow_embedded_strings: true +operand_sets: + immediate_8bit: + # one byte is interpreted as an immediate value + operand_values: + int8: + type: numeric + argument: + size: 8 + byte_align: true + endian: little + immediate_16bit: + # 2 bytes is interpreted as an immediate value + operand_values: + int16: + type: numeric + argument: + size: 16 + byte_align: true + endian: little + immediate_32bit: + # 4 bytes is interpreted as an immediate value + operand_values: + int32: + type: numeric + argument: + size: 32 + byte_align: true + endian: little + absolute_address: + # two bytes are interpreted as an address + operand_values: + address: + type: address + argument: + size: 16 + byte_align: true + endian: little + relative_address: + # two byte is interpreted as a relative address + operand_values: + address: + type: address + argument: + size: 16 + byte_align: true + endian: little + address_lsb: + # one byte is interpreted as an address's LSB and the address MSB + # is taken from the current instruction address + operand_values: + address: + type: address + argument: + size: 8 + byte_align: true + endian: little + slice_lsb: true + match_address_msb: true + offset: + # one byte is interpreted as an offset + operand_values: + int8: + type: numeric + argument: + size: 8 + byte_align: true + endian: little + zero_page: + # one byte is interpreted as a zero page address + operand_values: + address: + type: address + argument: + size: 8 + byte_align: true + endian: little + slice_lsb: true + memory_zone: ZERO_PAGE + relative_zero_page: + # one byte is interpreted as a zero page address to a word that + # in turn is used as a relative address + operand_values: + address: + type: address + argument: + size: 8 + byte_align: true + endian: little + slice_lsb: true + memory_zone: ZERO_PAGE +predefined: + memory_zones: + - name: ZERO_PAGE + start: 0x0000 + end: 0x00FF + - name: ZERO_PAGE_APPS + start: 0x0000 + end: 0x007F + - name: ZERO_PAGE_OS + start: 0x0080 + end: 0x00FF + - name: USER_APPS + start: 0x8000 + end: 0xEFFF + constants: + - # Start vector of the OS in RAM + name: _Start + value: 0xf000 + - # Hands back control to the input prompt + name: _Prompt + value: 0xf003 + - # Moves memory area (may be overlapping) + name: _MemMove + value: 0xf006 + - # Returns a pseudo-random byte (see _RandomState) + name: _Random + value: 0xf009 + - # Scans the PS/2 register for new input + name: _ScanPS2 + value: 0xf00c + - # Resets the state of PS/2 SHIFT, ALTGR, CTRL + name: _ResetPS2 + value: 0xf00f + - # Reads any input (PS/2 or serial) + name: _ReadInput + value: 0xf012 + - # Waits for any input (PS/2 or serial) + name: _WaitInput + value: 0xf015 + - # Reads a command line into _ReadBuffer + name: _ReadLine + value: 0xf018 + - # Skips whitespaces (<= 39) in command line + name: _SkipSpace + value: 0xf01b + - # Parses command line input for a HEX value + name: _ReadHex + value: 0xf01e + - # Waits for a UART transmission to complete + name: _SerialWait + value: 0xf021 + - # Transmits a zero-terminated string via UART + name: _SerialPrint + value: 0xf024 + - # Searches for file given by _ReadPtr + name: _FindFile + value: 0xf027 + - # Loads a file given by _ReadPtr + name: _LoadFile + value: 0xf02a + - # Saves data to file defined at _ReadPtr + name: _SaveFile + value: 0xf02d + - # Clears the video RAM including blanking areas + name: _ClearVRAM + value: 0xf030 + - # Clears the visible video RAM (viewport) + name: _Clear + value: 0xf033 + - # Clears the current row from cursor pos onwards + name: _ClearRow + value: 0xf036 + - # Scrolls up the viewport by 8 pixels + name: _ScrollUp + value: 0xf039 + - # Scrolls down the viewport by 8 pixels + name: _ScrollDn + value: 0xf03c + - # Outputs a char at the cursor pos (non-advancing) + name: _Char + value: 0xf03f + - # Prints a char at the cursor pos (advancing) + name: _PrintChar + value: 0xf042 + - # Prints a zero-terminated immediate string + name: _Print + value: 0xf045 + - # Prints a zero-terminated string at an address + name: _PrintPtr + value: 0xf048 + - # Prints a HEX number (advancing) + name: _PrintHex + value: 0xf04b + - # Sets a pixel at position (x, y) + name: _SetPixel + value: 0xf04e + - # Draws a line using Bresenham’s algorithm + name: _Line + value: 0xf051 + - # Draws a rectangle at (x, y) of size (w, h) + name: _Rect + value: 0xf054 + - # Clears a pixel at position (x, y) + name: _ClearPixel + value: 0xf057 + - # Horizontal cursor position (see _Print) + name: _XPos + value: 0x00c0 + - # Vertical cursor position (see _Print) + name: _YPos + value: 0x00c1 + - # _Random state seed + name: _RandomState + value: 0x00c2 + - # Number parsed by _ReadHex + name: _ReadNum + value: 0x00c6 + - # Command line parsing pointer + name: _ReadPtr + value: 0x00c9 + - # Address of command line input buffer + name: _ReadBuffer + value: 0x00cd +instructions: + nop: + # No operation + bytecode: + value: 0x00 + size: 8 + out: + # Output A to UART: UART = A + bytecode: + value: 0x01 + size: 8 + int: + # Read UART input to A: A = UART + bytecode: + value: 0x02 + size: 8 + ink: + # Read PS/2 input to A: A = PS2 + bytecode: + value: 0x03 + size: 8 + win: + # Wait for input + bytecode: + value: 0x04 + size: 8 + sec: + # Set carry flag (C=1) + bytecode: + value: 0x05 + size: 8 + clc: + # Clear carry flag (C=0) + bytecode: + value: 0x06 + size: 8 + ll0: + # Logical left-shift A 0 steps (C=0) + bytecode: + value: 0x07 + size: 8 + ll1: + # Logical left-shift A 1 step (C=0) + bytecode: + value: 0x08 + size: 8 + ll2: + # Logical left-shift A 2 steps (C=0) + bytecode: + value: 0x09 + size: 8 + ll3: + # Logical left-shift A 3 steps (C=0) + bytecode: + value: 0x0a + size: 8 + ll4: + # Logical left-shift A 4 steps (C=0) + bytecode: + value: 0x0b + size: 8 + ll5: + # Logical left-shift A 5 steps (C=0) + bytecode: + value: 0x0c + size: 8 + ll6: + # Logical left-shift A 6 steps (C=0) + bytecode: + value: 0x0d + size: 8 + ll7: + # Logical left-shift A 7 steps (C=0) + bytecode: + value: 0x0e + size: 8 + rl0: + # Rotate left A 0 steps via C (= RR9) + bytecode: + value: 0x0f + size: 8 + rl1: + # Rotate left A 1 step via C (= RR8) + bytecode: + value: 0x10 + size: 8 + rl2: + # Rotate left A 2 steps via C (= RR7) + bytecode: + value: 0x11 + size: 8 + rl3: + # Rotate left A 3 steps via C (= RR6) + bytecode: + value: 0x12 + size: 8 + rl4: + # Rotate left A 4 steps via C (= RR5) + bytecode: + value: 0x13 + size: 8 + rl5: + # Rotate left A 5 steps via C (= RR4) + bytecode: + value: 0x14 + size: 8 + rl6: + # Rotate left A 6 steps via C (= RR3) + bytecode: + value: 0x15 + size: 8 + rl7: + # Rotate left A 7 steps via C (= RR2) + bytecode: + value: 0x16 + size: 8 + rr1: + # Rotate right A 1 step via C (= RL8) + bytecode: + value: 0x17 + size: 8 + lr0: + # Logical right-shift A 0 steps (C=0) (= RR0, RL9) + bytecode: + value: 0x18 + size: 8 + lr1: + # Logical right-shift A 1 step (C=0) + bytecode: + value: 0x19 + size: 8 + lr2: + # Logical right-shift A 2 steps (C=0) + bytecode: + value: 0x1a + size: 8 + lr3: + # Logical right-shift A 3 steps (C=0) + bytecode: + value: 0x1b + size: 8 + lr4: + # Logical right-shift A 4 steps (C=0) + bytecode: + value: 0x1c + size: 8 + lr5: + # Logical right-shift A 5 steps (C=0) + bytecode: + value: 0x1d + size: 8 + lr6: + # Logical right-shift A 6 steps (C=0) + bytecode: + value: 0x1e + size: 8 + lr7: + # Logical right-shift A 7 steps (C=0) + bytecode: + value: 0x1f + size: 8 + llz: + # Logical shift left *Z 1 step (C=0) + bytecode: + value: 0x20 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + llb: + # Logical shift byte left 1 step (C=0) + bytecode: + value: 0x21 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + llv: + # Logical shift fast word left 1 step (C=0) + bytecode: + value: 0x22 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + llw: + # Logical shift word left 1 step (C=0) + bytecode: + value: 0x23 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + llq: + # Logical shift fast long left 1 step (C=0) + bytecode: + value: 0x24 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + lll: + # Logical shift long left 1 step (C=0) + bytecode: + value: 0x25 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + lrz: + # Logical shift right zero-page byte 1 step (C=0) + bytecode: + value: 0x26 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + lrb: + # Logical shift right abs byte 1 step (C=0) + bytecode: + value: 0x27 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + rlz: + # Rotate left zero-page byte 1 step via C + bytecode: + value: 0x28 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + rlb: + # Rotate left byte at abs addr 1 step via C + bytecode: + value: 0x29 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + rlv: + # Rotate left zero-page word 1 step via C + bytecode: + value: 0x2a + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + rlw: + # Rotate left word at abs addr 1 step via C + bytecode: + value: 0x2b + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + rlq: + # Rotate left zero-page long 1 step via C + bytecode: + value: 0x2c + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + rll: + # Rotate left abs long 1 step via C + bytecode: + value: 0x2d + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + rrz: + # Rotate right zero-page byte 1 step via C + bytecode: + value: 0x2e + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + rrb: + # Rotate right byte at abs addr 1 step via C + bytecode: + value: 0x2f + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + not: + # Bitwise NOT A: A = ~A + bytecode: + value: 0x30 + size: 8 + noz: + # Bitwise NOT Z: *Z = ~(*Z) + bytecode: + value: 0x31 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + nob: + # Bitwise NOT byte: *addr = ~(*addr) + bytecode: + value: 0x32 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + nov: + # Bitwise NOT zero-page word: *V = ~(*V) + bytecode: + value: 0x33 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + now: + # Bitwise NOT word at abs address + bytecode: + value: 0x34 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + noq: + # Bitwise NOT zero-page long + bytecode: + value: 0x35 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + nol: + # Bitwise NOT long at abs address + bytecode: + value: 0x36 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + neg: + # Negate A: A = -A + bytecode: + value: 0x37 + size: 8 + nez: + # Negate zero-page byte: *Z = -(*Z) + bytecode: + value: 0x38 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + neb: + # Negate byte at abs address: *addr = -(*addr) + bytecode: + value: 0x39 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + nev: + # Negate zero-page word: *V = -(*V) + bytecode: + value: 0x3a + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + new: + # Negate word at abs address + bytecode: + value: 0x3b + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + neq: + # Negate zero-page long + bytecode: + value: 0x3c + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + nel: + # Negate long at abs address + bytecode: + value: 0x3d + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ani: + # Bitwise AND immediate: A = A & imm + bytecode: + value: 0x3e + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + anz: + # Bitwise AND zero-page byte: A = A & *Z + bytecode: + value: 0x3f + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + anb: + # Bitwise AND byte at abs address: A = A & *addr + bytecode: + value: 0x40 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ant: + # Bitwise AND byte at rel zero-page address: A = A & *(*Z) + bytecode: + value: 0x41 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + anr: + # Bitwise AND byte at rel address: A = A & *(*addr) + bytecode: + value: 0x42 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + an.z: + # Bitwise AND A to zero-page byte: *Z = *Z & A + bytecode: + value: 0x43 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + an.b: + # Bitwise AND A to byte at abs address: *addr = *addr & A + bytecode: + value: 0x44 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ori: + # Bitwise OR immediate: A = A | imm + bytecode: + value: 0x45 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + orz: + # Bitwise OR zero-page byte: A = A | *Z + bytecode: + value: 0x46 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + orb: + # Bitwise OR byte at abs address: A = A | *addr + bytecode: + value: 0x47 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ort: + # Bitwise OR byte at rel zero-page address: A = A | *(*Z) + bytecode: + value: 0x48 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + orr: + # Bitwise OR byte at rel address: A = A | *(*addr) + bytecode: + value: 0x49 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + or.z: + # Bitwise OR A to zero-page byte: *Z = *Z | A + bytecode: + value: 0x4a + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + or.b: + # Bitwise OR A to byte at abs address: *addr = *addr | A + bytecode: + value: 0x4b + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + xri: + # Bitwise XOR immediate: A = A ^ imm + bytecode: + value: 0x4c + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + xrz: + # Bitwise XOR zero-page byte: A = A ^ *Z + bytecode: + value: 0x4d + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + xrb: + # Bitwise XOR byte at abs address: A = A ^ *addr + bytecode: + value: 0x4e + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + xrt: + # Bitwise XOR byte at rel zero-page address: A = A ^ *(*Z) + bytecode: + value: 0x4f + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + xrr: + # Bitwise XOR byte at rel address: A = A ^ *(*addr) + bytecode: + value: 0x50 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + xr.z: + # Bitwise XOR A to zero-page byte: *Z = *Z ^ A + bytecode: + value: 0x51 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + xr.b: + # Bitwise XOR A to byte at abs address: *addr = *addr ^ A + bytecode: + value: 0x52 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + fne: + # Fast branch on non-zero + bytecode: + value: 0x53 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + feq: + # Fast branch on equal + bytecode: + value: 0x54 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fcc: + # Fast branch on carry clear + bytecode: + value: 0x55 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fcs: + # Fast branch on carry set + bytecode: + value: 0x56 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fpl: + # Fast branch on plus + bytecode: + value: 0x57 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fmi: + # Fast branch on minus + bytecode: + value: 0x58 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fgt: + # Fast branch on greater + bytecode: + value: 0x59 + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fle: + # Fast branch on less or equal + bytecode: + value: 0x5a + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + fpa: + # Fast jump to lsb address + bytecode: + value: 0x5b + size: 8 + operands: + count: 1 + operand_sets: + list: + - address_lsb + bne: + # Branch on non-zero + bytecode: + value: 0x5c + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + beq: + # Branch on zero + bytecode: + value: 0x5d + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + bcc: + # Branch on carry clear + bytecode: + value: 0x5e + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + bcs: + # Branch on carry set + bytecode: + value: 0x5f + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + bpl: + # Branch on plus + bytecode: + value: 0x60 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + bmi: + # Branch on minus + bytecode: + value: 0x61 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + bgt: + # Branch on greater + bytecode: + value: 0x62 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ble: + # Branch on less or equal + bytecode: + value: 0x63 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + jpa: + # Jump to address + bytecode: + value: 0x64 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + jpr: + # Jump to relative address + bytecode: + value: 0x65 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + jar: + # Jump A-indexed to rel address: PC = *(addr + A) + bytecode: + value: 0x66 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + jps: + # Jump to subroutine + bytecode: + value: 0x67 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + jas: + # Jump to subroutine conserving A + bytecode: + value: 0x68 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + rts: + # Return from subroutine + bytecode: + value: 0x69 + size: 8 + phs: + # Push A to stack + bytecode: + value: 0x6a + size: 8 + pls: + # Pull A from stack + bytecode: + value: 0x6b + size: 8 + lds: + # Load from stack: A = *(0xff00 + SP + off) + bytecode: + value: 0x6c + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + sts: + # Store to stack: *(0xff00 + SP + off) = A + bytecode: + value: 0x6d + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + rdb: + # Read FLASH data from abs 3-byte address + bytecode: + value: 0x6e + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_16bit # flash address + - immediate_8bit # bank + rdr: + # Read FLASH data from rel 3-byte address + bytecode: + value: 0x6f + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + rap: + # Read FLASH data from A-indexed 3-byte address A = *(pg<<8 + A) + bytecode: + value: 0x70 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit # page + - immediate_8bit # bank + rzp: + # Read Z-indexed FLASH data: A = *(pg<<8 + *Z) + bytecode: + value: 0x71 + size: 8 + operands: + count: 3 + operand_sets: + list: + - zero_page + - immediate_8bit # page + - immediate_8bit # bank + wdb: + # Write FLASH data to abs 3-byte address + bytecode: + value: 0x72 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_16bit # flash address + - immediate_8bit # bank + wdr: + # Write FLASH data to rel 3-byte address + bytecode: + value: 0x73 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + ldi: + # Load immediate value to A: A = imm + bytecode: + value: 0x74 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + ldz: + # Load zero-page byte to A: A = *Z + bytecode: + value: 0x75 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + ldb: + # Load byte at abs address to A: A = *addr + bytecode: + value: 0x76 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ldt: + # Load byte at rel zero-page address to A: A = *(*Z) + bytecode: + value: 0x77 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + ldr: + # Load byte at rel address to A: A = *(*addr) + bytecode: + value: 0x78 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + lap: + # Load A-indexed byte to A: A = *(pg<<8 + A) + bytecode: + value: 0x79 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit # page + lab: + # Load A A-indexed from addr: A = *(addr + A) + bytecode: + value: 0x7a + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + lzp: + # Load Z-indexed byte to A: A = *(pg<<8 + *Z) + bytecode: + value: 0x7b + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - immediate_8bit # page + lzb: + # Load Z-indexed byte from addr: A = *(addr + *Z) + bytecode: + value: 0x7c + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + stz: + # Store A to zero-page byte: *Z = A + bytecode: + value: 0x7d + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + stb: + # Store A to byte at abs address: *addr = A + bytecode: + value: 0x7e + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + stt: + # Store A to byte at rel zero-page address: *(*Z) = A + bytecode: + value: 0x7f + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + str: + # Store A to byte at rel address: *(*addr) = A + bytecode: + value: 0x80 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + szp: + # Store Z-indexed A: *(pg<<8 + *Z) = A + bytecode: + value: 0x81 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - immediate_8bit # page + miz: + # Move immediate to zero-page byte: *Z = imm + bytecode: + value: 0x82 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + mib: + # Move immediate to byte at abs address: *addr = imm + bytecode: + value: 0x83 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + mit: + # Move immediate to byte at rel zero-page address: *(*Z) = imm + bytecode: + value: 0x84 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_zero_page + mir: + # Move immediate to byte at rel address: *(*addr) = imm + bytecode: + value: 0x85 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_address + miv: + # Move immediate word to zero-page word: *V = imm + bytecode: + value: 0x86 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_16bit + - zero_page + miw: + # Move immediate word to word at abs address: *addr = imm + bytecode: + value: 0x87 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_16bit + - absolute_address + mzz: + # Move zero-page byte to zero-page byte: *Z2 = *Z1 + bytecode: + value: 0x88 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + mzb: + # Move zero-page byte to byte at abs address: *addr = *Z + bytecode: + value: 0x89 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + mbz: + # Move byte at abs address to zero-page byte: *Z = *addr + bytecode: + value: 0x8a + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + mbb: + # Move byte at abs address to byte at abs address: *addr2 = *addr1 + bytecode: + value: 0x8b + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - absolute_address + mvv: + # Move zero-page word to zero-page word: *V2 = *V1 + bytecode: + value: 0x8c + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + mwv: + # Move word at abs address to zero-page word: *V = *addr + bytecode: + value: 0x8d + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + clz: + # Clear zero-page byte: *Z = 0 + bytecode: + value: 0x8e + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + clb: + # Clear byte at abs address: *addr = 0 + bytecode: + value: 0x8f + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + clv: + # Clear zero-page word: *V = 0 + bytecode: + value: 0x90 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + clw: + # Clear word at abs address: *addr = 0 + bytecode: + value: 0x91 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + clq: + # Clear zero-page long: *Q = 0 + bytecode: + value: 0x92 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + cll: + # Clear long at abs address: *addr = 0 + bytecode: + value: 0x93 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + inc: + # Increment A: A++ + bytecode: + value: 0x94 + size: 8 + inz: + # Increment zero-page byte: (*Z)++ + bytecode: + value: 0x95 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + inb: + # Increment byte at abs address: (*addr)++ + bytecode: + value: 0x96 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + inv: + # Increment zero-page word: (*V) += 0x0001 + bytecode: + value: 0x97 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + inw: + # Increment word at abs address: (*addr) += 0x0001 + bytecode: + value: 0x98 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + inq: + # Increment zero-page long: (*Q) += 0x00000001 + bytecode: + value: 0x99 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + inl: + # Increment long at abs address: (*addr) += 0x00000001 + bytecode: + value: 0x9a + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + dec: + # Decrement A: A-- + bytecode: + value: 0x9b + size: 8 + dez: + # Decrement zero-page byte: (*Z)-- + bytecode: + value: 0x9c + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + deb: + # Decrement byte at abs address: (*addr)-- + bytecode: + value: 0x9d + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + dev: + # Decrement zero-page word: (*V) -= 0x0001 + bytecode: + value: 0x9e + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + dew: + # Decrement word at abs address: (*addr) -= 0x0001 + bytecode: + value: 0x9f + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + deq: + # Decrement zero-page long: (*Q) -= 0x00000001 + bytecode: + value: 0xa0 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + del: + # Decrement long at abs address: (*addr) -= 0x00000001 + bytecode: + value: 0xa1 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + adi: + # Add immediate to A: A += imm + bytecode: + value: 0xa2 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + adz: + # Add zero-page byte to A: A += *Z + bytecode: + value: 0xa3 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + adb: + # Add byte at abs address to A: A += *addr + bytecode: + value: 0xa4 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + adt: + # Add byte at rel zero-page address to A: A += *(*Z) + bytecode: + value: 0xa5 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + adr: + # Add byte at rel address to A: A += *(*addr) + bytecode: + value: 0xa6 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + ad.z: + # Add A to zero-page byte: *Z += A + bytecode: + value: 0xa7 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + ad.b: + # Add A to byte at abs address: *addr += A + bytecode: + value: 0xa8 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ad.t: + # Add A to byte at rel zero-page address: *(*Z) += A + bytecode: + value: 0xa9 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + ad.r: + # Add A to byte at rel address: *(*addr) += A + bytecode: + value: 0xaa + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + adv: + # Add A to zero-page word: *V = *V + A + bytecode: + value: 0xab + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + adw: + # Add A to word at abs address: *addr = *addr + A + bytecode: + value: 0xac + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + adq: + # Add A to zero-page long: *Q = *Q + A + bytecode: + value: 0xad + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + adl: + # Add A to long at abs address: *addr = *addr + A + bytecode: + value: 0xae + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + aiz: + # Add immediate to zero-page byte: *Z += imm + bytecode: + value: 0xaf + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + aib: + # Add immediate to byte at abs address: *addr += imm + bytecode: + value: 0xb0 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + ait: + # Add immediate to byte at rel zero-page address: *(*Z) += imm + bytecode: + value: 0xb1 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_zero_page + air: + # Add immediate to byte at rel address: *(*addr) += imm + bytecode: + value: 0xb2 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_address + aiv: + # Add immediate byte to zero-page word: *V += imm + bytecode: + value: 0xb3 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + aiw: + # Add immediate byte to word at abs address: *addr += imm + bytecode: + value: 0xb4 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + aiq: + # Add immediate byte to zero-page long: *Q += imm + bytecode: + value: 0xb5 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + ail: + # Add immediate byte to long at abs address: *addr += imm + bytecode: + value: 0xb6 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + azz: + # Add zero-page byte to zero-page byte: *Z2 += *Z1 + bytecode: + value: 0xb7 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + azb: + # Add zero-page byte to byte at abs address: *addr += *Z + bytecode: + value: 0xb8 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + azv: + # Add zero-page byte to zero-page word: *V = *V + *Z + bytecode: + value: 0xb9 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z + - zero_page # V + azw: + # Add zero-page byte to word at abs address: *addr = *addr + *Z + bytecode: + value: 0xba + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + azq: + # Add zero-page byte to zero-page long: *Q = *Q + *Z + bytecode: + value: 0xbb + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z + - zero_page # Q + azl: + # Add zero-page byte to long at abs address: *addr = *addr + *Z + bytecode: + value: 0xbc + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + abz: + # Add byte at abs address to zero-page byte: *Z += *addr + bytecode: + value: 0xbd + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + abb: + # Add byte at abs address to byte at abs address: *addr2 = *addr2 + *addr1 + bytecode: + value: 0xbe + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address # addr1 + - absolute_address # addr2 + abv: + # Add byte at abs address to zero-page word: *V = *V + *addr + bytecode: + value: 0xbf + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + abw: + # Add byte at abs address to word at abs address: *addr2 = *addr2 + *addr1 + bytecode: + value: 0xc0 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address # addr1 + - absolute_address # addr2 + abq: + # Add byte at abs address to zero-page long: *Q = *Q + *addr + bytecode: + value: 0xc1 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + avv: + # Add zero-page word to zero-page word: *V2 = *V2 + *V1 + bytecode: + value: 0xc2 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # V1 + - zero_page # V2 + sui: + # Subtract immediate from A: A -= imm + bytecode: + value: 0xc3 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + suz: + # Subtract zero-page byte from A: A -= *Z + bytecode: + value: 0xc4 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + sub: + # Subtract byte at abs address from A: A -= *addr + bytecode: + value: 0xc5 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + sut: + # Subtract byte at rel zero-page address from A: A -= *(*Z) + bytecode: + value: 0xc6 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + sur: + # Subtract byte at rel address from A: A -= *(*addr) + bytecode: + value: 0xc7 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + su.z: + # Subtract A from zero-page byte: *Z -= A + bytecode: + value: 0xc8 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + su.b: + # Subtract A from byte at abs address: *addr -= A + bytecode: + value: 0xc9 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + su.t: + # Subtract A from byte at rel zero-page address: *(*Z) -= A + bytecode: + value: 0xca + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + su.r: + # Subtract A from byte at rel address: *(*addr) -= A + bytecode: + value: 0xcb + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + suv: + # Subtract A from zero-page word: *V = *V - A + bytecode: + value: 0xcc + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + suw: + # Subtract A from word at abs address: *addr = *addr - A + bytecode: + value: 0xcd + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + suq: + # Subtract A from zero-page long: *Q = *Q - A + bytecode: + value: 0xce + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + su.l: + # Subtract A from long at abs address: *addr = *addr - A + bytecode: + value: 0xcf + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + siz: + # Subtract immediate from zero-page byte: *Z -= imm + bytecode: + value: 0xd0 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + sib: + # Subtract immediate from byte at abs address: *addr -= imm + bytecode: + value: 0xd1 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + sit: + # Subtract immediate from byte at rel zero-page address: *(*Z) -= imm + bytecode: + value: 0xd2 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_zero_page + sir: + # Subtract immediate from byte at rel address: *(*addr) -= imm + bytecode: + value: 0xd3 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_address + siv: + # Subtract immediate byte from zero-page word: *V -= imm + bytecode: + value: 0xd4 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + siw: + # Subtract immediate byte from word at abs address: *addr -= imm + bytecode: + value: 0xd5 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + siq: + # Subtract immediate byte from zero-page long: *Q -= imm + bytecode: + value: 0xd6 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + sil: + # Subtract immediate byte from long at abs address: *addr -= imm + bytecode: + value: 0xd7 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + szz: + # Subtract zero-page byte from zero-page byte: *Z2 -= *Z1 + bytecode: + value: 0xd8 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z1 + - zero_page # Z2 + szb: + # Subtract zero-page byte from byte at abs address: *addr -= *Z + bytecode: + value: 0xd9 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + szv: + # Subtract zero-page byte from zero-page word: *V = *V - *Z + bytecode: + value: 0xda + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z + - zero_page # V + szw: + # Subtract zero-page byte from word at abs address: *addr = *addr - *Z + bytecode: + value: 0xdb + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + szq: + # Subtract zero-page byte from zero-page long: *Q = *Q - *Z + bytecode: + value: 0xdc + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z + - zero_page # Q + szl: + # Subtract zero-page byte from long at abs address: *addr = *addr - *Z + bytecode: + value: 0xdd + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + sbz: + # Subtract byte at abs address from zero-page byte: *Z -= *addr + bytecode: + value: 0xde + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + sbb: + # Subtract byte at abs address from byte at abs + # address: *addr2 = *addr2 - *addr1 + bytecode: + value: 0xdf + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address # addr1 + - absolute_address # addr2 + sbv: + # Subtract byte at abs address from zero-page word: *V = *V - *addr + bytecode: + value: 0xe0 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + sbw: + # Subtract byte at abs address from word at abs + # address: *addr2 = *addr2 - *addr1 + bytecode: + value: 0xe1 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address # addr1 + - absolute_address # addr2 + sbq: + # Subtract byte at abs address from zero-page long: *Q = *Q - *addr + bytecode: + value: 0xe2 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + svv: + # Subtract zero-page word from zero-page word: *V2 = *V2 - *V1 + bytecode: + value: 0xe3 + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # V1 + - zero_page # V2 + cpi: + # Compare immediate with A: A - imm + bytecode: + value: 0xe4 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + cpz: + # Compare zero-page byte with A: A - *Z + bytecode: + value: 0xe5 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + cpb: + # Compare byte at abs address with A: A - *addr + bytecode: + value: 0xe6 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + cpt: + # Compare byte at rel zero-page address with A: A - *(*Z) + bytecode: + value: 0xe7 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_zero_page + cpr: + # Compare byte at rel address with A: A - *(*addr) + bytecode: + value: 0xe8 + size: 8 + operands: + count: 1 + operand_sets: + list: + - relative_address + ciz: + # Compare immediate with zero-page byte: *Z - imm + bytecode: + value: 0xe9 + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - zero_page + cib: + # Compare immediate with byte at abs address: *addr - imm + bytecode: + value: 0xea + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - absolute_address + cit: + # Compare immediate with byte at rel zero-page address: *(*Z) - imm + bytecode: + value: 0xeb + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_zero_page + cir: + # Compare immediate with byte at rel address: *(*addr) - imm + bytecode: + value: 0xec + size: 8 + operands: + count: 2 + operand_sets: + list: + - immediate_8bit + - relative_address + czz: + # Compare zero-page byte with zero-page byte: *Z2 - *Z1 + bytecode: + value: 0xed + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page # Z1 + - zero_page # Z2 + czb: + # Compare zero-page byte with byte at abs address: *addr - *Z + bytecode: + value: 0xee + size: 8 + operands: + count: 2 + operand_sets: + list: + - zero_page + - absolute_address + cbz: + # Compare abs byte to zero-page byte: A = *addr - *Z + bytecode: + value: 0xef + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + cbb: + # Compare byte at abs address with byte at abs address: *addr2 - *addr1 + bytecode: + value: 0xf0 + size: 8 + operands: + count: 2 + operand_sets: + list: + - absolute_address # addr1 + - absolute_address # addr2 + aci: + # Add immediate value to A with C: A = A + imm + C + bytecode: + value: 0xf1 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + acz: + # Add zero-page byte to A with C: A = A + *Z + C + bytecode: + value: 0xf2 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + acb: + # Add byte at abs address to A with C: A = A + *addr + C + bytecode: + value: 0xf3 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + ac.z: + # Add A to zero-page byte with C: *Z = *Z + A + C + bytecode: + value: 0xf4 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + ac.b: + # Add A to byte at abs address with C: *addr = *addr + A + C + bytecode: + value: 0xf5 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + acv: + # Add A to zero-page word with C: *V = *V + A + C + bytecode: + value: 0xf6 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + acw: + # Add A to word at abs address with C: *addr = *addr + A + C + bytecode: + value: 0xf7 + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + sci: + # Sub imm value from A with C: A = A - imm - 1 + C + bytecode: + value: 0xf8 + size: 8 + operands: + count: 1 + operand_sets: + list: + - immediate_8bit + scz: + # Sub zero-page byte from A with C: A = A - *Z - 1 + C + bytecode: + value: 0xf9 + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + scb: + # Sub byte at abs address from A with C: A = A - *addr - 1 + C + bytecode: + value: 0xfa + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + sc.z: + # Sub A from zero-page byte with C: *Z = *Z - A - 1 + C + bytecode: + value: 0xfb + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + sc.b: + # Sub A from byte at abs address with C: *addr = *addr - A - 1 + C + bytecode: + value: 0xfc + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address + scv: + # Sub A from zero-page word with C: *V = *V - A - 1 + C + bytecode: + value: 0xfd + size: 8 + operands: + count: 1 + operand_sets: + list: + - zero_page + scw: + # Sub A from word at abs address with C: *addr = *addr - A - 1 + C + bytecode: + value: 0xfe + size: 8 + operands: + count: 1 + operand_sets: + list: + - absolute_address +macros: + spinit: + - operands: + count: 0 + instructions: + - "mib 0xfe,0xffff" + phsi: + - operands: + count: 1 + operand_sets: + list: + - immediate_8bit + instructions: + - "ldi @ARG(0)" + - "phs" + phs2i: + - operands: + count: 1 + operand_sets: + list: + - immediate_16bit + instructions: + - "ldi LSB(@ARG(0))" + - "phs" + - "ldi BYTE1(@ARG(0))" + - "phs" + phs4i: + - operands: + count: 1 + specific_operands: + immediate: + list: + uint32: + type: numeric + argument: + size: 32 + byte_align: true + instructions: + - "ldi BYTE0(@ARG(0))" + - "phs" + - "ldi BYTE1(@ARG(0))" + - "phs" + - "ldi BYTE2(@ARG(0))" + - "phs" + - "ldi BYTE3(@ARG(0))" + - "phs" + phs4a: + # push 4 bytes of an absolute address to the stack + # stack is arranged as big-endian, address is little-endian + - operands: + count: 1 + operand_sets: + list: + - absolute_address + instructions: + - "ldb @ARG(0)+0" + - "phs" + - "ldb @ARG(0)+1" + - "phs" + - "ldb @ARG(0)+2" + - "phs" + - "ldb @ARG(0)+3" + - "phs" + phsptr: + # push an immediate absolute address to the stack per MinOS calling convention + - operands: + count: 1 + operand_sets: + list: + - absolute_address + instructions: + - "ldi BYTE0(@ARG(0))" + - "phs" + - "ldi BYTE1(@ARG(0))" + - "phs" + phs2s: + # push 2 bytes at (current) stack offset to the stack + - operands: + count: 1 + operand_sets: + list: + - offset + instructions: + - "lds @ARG(0)+1+0" + - "phs" + - "lds @ARG(0)+0+1" + - "phs" + phs4s: + # push 4 bytes at (current) stack offset to the stack + - operands: + count: 1 + operand_sets: + list: + - offset + instructions: + - "lds @ARG(0)+3+0" + - "phs" + - "lds @ARG(0)+2+1" + - "phs" + - "lds @ARG(0)+1+2" + - "phs" + - "lds @ARG(0)+0+3" + - "phs" + phsz: + # push a zero-page byte to the stack + - operands: + count: 1 + operand_sets: + list: + - zero_page + instructions: + - "ldz @ARG(0)" + - "phs" + phsv: + # push a zero-page word to the stack + # stack is arranged as big-endian, word in zero-page is little-endian + - operands: + count: 1 + operand_sets: + list: + - zero_page + instructions: + - "ldz @ARG(0)+0" + - "phs" + - "ldz @ARG(0)+1" + - "phs" + phsq: + # push a zero page long to the stack + # stack is arranged as big-endian, long in zero-page is little-endian + - operands: + count: 1 + operand_sets: + list: + - zero_page + instructions: + - "ldz @ARG(0)+0" + - "phs" + - "ldz @ARG(0)+1" + - "phs" + - "ldz @ARG(0)+2" + - "phs" + - "ldz @ARG(0)+3" + - "phs" + pls2: + # pull 2 bytes from the stack + - operands: + count: 0 + instructions: + - "pls" + - "pls" + pls4: + - operands: + count: 0 + instructions: + - "pls" + - "pls" + - "pls" + - "pls" + mws2: + # move 2-byte word at absolute address to stack at offset + # stack is arranged as big-endian, word at abs address is little-endian + - operands: + count: 2 + operand_sets: + list: + - absolute_address + - offset + instructions: + - "ldb @ARG(0)+0" + - "sts @ARG(1)+1" + - "ldb @ARG(0)+1" + - "sts @ARG(1)+0" + ms2w: + # move 2-byte word from stack at offset to absolute address + # stack is arranged as big-endian, word at abs address is little-endian + - operands: + count: 2 + operand_sets: + list: + - offset + - absolute_address + instructions: + - "lds @ARG(0)+1" + - "stb @ARG(1)+0" + - "lds @ARG(0)+0" + - "stb @ARG(1)+1" + ms2v: + # move 2 bytes from stack to zero-page word + # stack is arranged as big-endian, word in zero-page is little-endian + - operands: + count: 2 + operand_sets: + list: + - offset + - zero_page + instructions: + - "lds @ARG(0)+0" + - "stz @ARG(1)+1" + - "lds @ARG(0)+1" + - "stz @ARG(1)+0" + mvs2: + # move 4 bytes from zero-page word to stack starting at passed offset + # stack is arranged as big-endian, word in zero-page is little-endian + - operands: + count: 2 + operand_sets: + list: + - zero_page + - offset + instructions: + - "ldz @ARG(0)+1" + - "sts @ARG(1)+0" + - "ldz @ARG(0)+0" + - "sts @ARG(1)+1" + ms4q: + # move 4 bytes from stack starting at passed offset to zero-page long + # stack is arranged as big-endian, long in zero-page is little-endian + - operands: + count: 2 + operand_sets: + list: + - offset + - zero_page + instructions: + - "lds @ARG(0)+3" + - "stz @ARG(1)+0" + - "lds @ARG(0)+2" + - "stz @ARG(1)+1" + - "lds @ARG(0)+1" + - "stz @ARG(1)+2" + - "lds @ARG(0)+0" + - "stz @ARG(1)+3" + mqs4: + # move 4 bytes from zero-page long to stack starting at passed offset + # stack is arranged as big-endian, long in zero-page is little-endian + - operands: + count: 2 + operand_sets: + list: + - zero_page + - offset + instructions: + - "ldz @ARG(0)+3" + - "sts @ARG(1)+0" + - "ldz @ARG(0)+2" + - "sts @ARG(1)+1" + - "ldz @ARG(0)+1" + - "sts @ARG(1)+2" + - "ldz @ARG(0)+0" + - "sts @ARG(1)+3" + mls4: + # move a long (4 bytes) from abs address to stack starting at passed offset + # stack is arranged as big-endian, long at abs address is little-endian + - operands: + count: 2 + operand_sets: + list: + - absolute_address + - offset + instructions: + - "ldb @ARG(0)+3" + - "sts @ARG(1)+0" + - "ldb @ARG(0)+2" + - "sts @ARG(1)+1" + - "ldb @ARG(0)+1" + - "sts @ARG(1)+2" + - "ldb @ARG(0)+0" + - "sts @ARG(1)+3" + ms4l: + # move a long (4 bytes) from stack starting at passed offset to abs address + # stack is arranged as big-endian, long at abs address is little-endian + - operands: + count: 2 + operand_sets: + list: + - offset + - absolute_address + instructions: + - "lds @ARG(0)+3" + - "stb @ARG(1)+0" + - "lds @ARG(0)+2" + - "stb @ARG(1)+1" + - "lds @ARG(0)+1" + - "stb @ARG(1)+2" + - "lds @ARG(0)+0" + - "stb @ARG(1)+3" + aqq: + # add two zero-page longs. result in second zero page operand + - operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + instructions: + - "azz @ARG(0)+0,@ARG(1)+0" + - "ldz @ARG(0)+1" + - "ac.z @ARG(1)+1" + - "ldz @ARG(0)+2" + - "ac.z @ARG(1)+2" + - "ldz @ARG(0)+3" + - "ac.z @ARG(1)+3" + sqq: + # subtract two zero-page longs. *Q2 = *Q2 - Q1 + - operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + instructions: + - "szz @ARG(0)+0,@ARG(1)+0" + - "ldz @ARG(0)+2" + - "sc.z @ARG(1)+2" + - "ldz @ARG(0)+3" + - "sc.z @ARG(1)+3" + mqq: + # move zero-page long to zero-page long + - operands: + count: 2 + operand_sets: + list: + - zero_page + - zero_page + instructions: + - "mvv @ARG(0)+0,@ARG(1)+0" + - "mvv @ARG(0)+2,@ARG(1)+2" + mll: + # move long from abs address to abs address + - operands: + count: 2 + operand_sets: + list: + - absolute_address + - absolute_address + instructions: + - "mbb @ARG(0)+0,@ARG(1)+0" + - "mbb @ARG(0)+1,@ARG(1)+1" + - "mbb @ARG(0)+2,@ARG(1)+2" + - "mbb @ARG(0)+3,@ARG(1)+3" + mlq: + # move long from abs address to zero-page long + - operands: + count: 2 + operand_sets: + list: + - absolute_address + - zero_page + instructions: + - "mwv @ARG(0)+0,@ARG(1)+0" + - "mwv @ARG(0)+2,@ARG(1)+2" + miq: + # move 4 bytes long from immediate to zero-page long + - operands: + count: 2 + operand_sets: + list: + - immediate_16bit + - zero_page + instructions: + - "miv @ARG(0) & $0000FFFF ,@ARG(1)+0" + - "miv (@ARG(0) & $FFFF0000) >> 16,@ARG(1)+2" + inq_: + # increment zero-page long. This macro exists because the built-in instruction INQ has a bug. + - operands: + count: 1 + operand_sets: + list: + - zero_page + instructions: + - "inv @ARG(0)+0" + - "ldi 0" + - "ac.z @ARG(0)+2" + - "ldi 0" + - "ac.z @ARG(0)+3" diff --git a/examples/slu4-minimal-64x4/software/hello-world.min64x4 b/examples/slu4-minimal-64x4/software/hello-world.min64x4 new file mode 100644 index 0000000..f1a9234 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/hello-world.min64x4 @@ -0,0 +1,10 @@ +#require "slu4-min64x4-asm >= 1.1.0" + +.org $8000 +init: + spinit ; init stack pointer +main: + jps _Print "Hello, World!" ; jump to print function + +end: + jpa _Prompt ; jump to prompt function diff --git a/examples/slu4-minimal-64x4/software/mathlib32.min64x4 b/examples/slu4-minimal-64x4/software/mathlib32.min64x4 new file mode 100644 index 0000000..5171197 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/mathlib32.min64x4 @@ -0,0 +1,263 @@ +; Math Library 32-bit +; This library provides 32-bit math functions for unsigned and signed integers. +; +#require "slu4-min64x4-asm >= 1.1.0" +; Zero Page Usage +.memzone ZERO_PAGE_APPS +#mute +_temp_byte1: .byte 0 +_temp_long1: .zero 4 +_working_mem8: .zero 8 +_multiply_sign_byte: .byte 0 +_argX4: .zero 4 +_argY4: .zero 4 +_counter: .byte 0 + +#emit +.memzone USER_APPS +; compare_uint32 +; Compares two unsigned 32-bit values to determine equality +; X ? Y +; +; Arguments +; sp+3 : right Y value (4 bytes) +; sp+7 : left X value (4 bytes) +; +; Returns +; flags will be set per comparison +; +.align +compare_uint32ss: + ; load values into zero page and then use _compare_uint32_XY + ms4q 7,_argX4 + ms4q 3,_argY4 + jps _compare_uint32_XY + rts + +; compares high and low longs (4 byte) in _argX4 and _argY4. +; X ? Y +; +; Arguments +; X - _argX4 +; Y - _argY4 +; +; Returns +; flags will be set per comparison +_compare_uint32_XY: + czz _argY4+3,_argX4+3 fne .done + czz _argY4+2,_argX4+2 fne .done + czz _argY4+1,_argX4+1 fne .done + czz _argY4+0,_argX4+0 +.done: + rts + +; multiply_uint32 +; multiply unsigned 4 byte values X*Y, producing an 8 byte unsigned results +; +; multiply_int32 +; multiply signed 4 byte values X*Y, producing an 8 byte signed results +; +; Arguments +; sp+3 - value X (multiplier) (4 bytes) +; sp+7 - value Y (multiplicand) (4 bytes) +; +; Return Value +; sp+3 - results (8 bytes) +; +; Zero Page usage +; _working_mem8 - 8 bytes of working memory +; _multiply_sign_byte - 1 byte to store sign of results +; _argY4 - 4 bytes to store X value +; _counter - 1 byte counter + +multiply_uint32: + ; return is always positive + clz _multiply_sign_byte + ; set up 8 byte results memory block + clq _working_mem8+4 ; high long inialized to 0 + ms4q 3,_working_mem8+0 ; multiplier in low word + ms4q 7,_argY4 ; multiplicand in zero page variable + jpa _multiply + +multiply_int32: + ; set up 8 byte results memory block + clq _working_mem8+4 ; high long inialized to 0 + ms4q 3,_working_mem8+0 ; multiplier in low word + ms4q 7,_argY4 ; multiplicand in zero page variable + ; determine if result is going to be negative + ldz _working_mem8+3 ani %10000000 stz _multiply_sign_byte cpi 0 beq .check_multiplicand + ; negate mutiplier (stack is big endian) + neq _working_mem8+0 +.check_multiplicand: + ldz _argY4+3 ani %10000000 xr.z _multiply_sign_byte + ldz _argY4+3 ani %10000000 cpi 0 beq .done + ; negate mutiplicand (stack is big endian) + neq _argY4+0 +.done: + jpa _multiply + +_multiply: + ; set counter for 32 bits + miz 32,_counter +.mult_loop: + ; check to see if LSb of working memory is 1 + ldz _working_mem8+0 lr1 bcc .continue + ; add multiplicand to high word of results + aqq _argY4,_working_mem8+4 +.continue: + ; shift results right one. + lrz _working_mem8+7 + rrz _working_mem8+6 + rrz _working_mem8+5 + rrz _working_mem8+4 + rrz _working_mem8+3 + rrz _working_mem8+2 + rrz _working_mem8+1 + rrz _working_mem8+0 + ; decrement counter (placing it in A) and stop if 0 + dez _counter ciz 0,_counter bne .mult_loop +.set_sign: + ; check to see if result is negative: + ciz 0,_multiply_sign_byte beq .copy_results + ; take twos complement of 8-byte results + noq _working_mem8+0 + noq _working_mem8+4 + inw _working_mem8+0 bcc .copy_results ; if only INQ set the flags :-( + inw _working_mem8+2 bcc .copy_results + inw _working_mem8+4 bcc .copy_results + inw _working_mem8+6 +.copy_results: + ; the entire working memory is the 64-bit results + mqs4 _working_mem8+4,3+0 + mqs4 _working_mem8+0,3+4 + rts + +; divide32 +; Divides X by Y (note, unsigned only) +; +; Arguments: +; sp+3 : value X dividend (4 bytes) +; sp+7 : value Y divisor (4 bytes) +; +; Return Value: +; sp+3 : the quotient (replaces X) +; sp+7 : the remainder (replaces Y) +; +divide32: + ; first check divisor is not 0 + ms4q 7,_argX4 + clq _argY4 + jps _compare_uint32_XY + beq .divide_by_zero + ; check if dividend is 0 + ms4q 3,_argX4 + jps _compare_uint32_XY + beq .return_zero + ; check if divisor > dividend + ms4q 7,_argX4 ; get divisor + ms4q 3,_argY4 ; get dividend + jps _compare_uint32_XY + bgt .divisor_too_large +.start_division: + ; set up working memory: + ; little endian + ; _working_mem8+0 : init with dividend (4 bytes) --> becomes quotient + ; _working_mem8+4 : set to zero (4 bytes) --> becomes remainder + ; _temp_long1 : divisor + ; _temp_byte1 : carry bit + clz _temp_byte1 ; init carry bit + ms4q 3,_working_mem8+0 ; init low word with dividend + clq _working_mem8+4 ; init high word + ms4q 7,_temp_long1 ; divisor + miz 32,_counter ; init loop counter + +.div_loop: + ; shift working memory and add carry bit to the right side + jps .div_lsl64 + azz _temp_byte1,_working_mem8+0 ; add carry bit to low byte + clz _temp_byte1 ; clear carry bit + ; determine if we can do subtraction if _working_mem8 high long is larger than divisor + mqq _temp_long1,_argX4 ; set _argX4 to divisor + mqq _working_mem8+4,_argY4 ; set _argY4 to _working_mem8+4 is high word + jps _compare_uint32_XY + bgt .div_loop_continue +.div_loop_subtraction: + ; working value is equal to or larger than divsior + ; do the subtraction + sqq _temp_long1,_working_mem8+4 ; subtract divisor from high long + miz 1,_temp_byte1 ; set carry bit +.div_loop_continue: + ; decrement counter and check for 0 + dez _counter bne .div_loop + + +.division_done: + ; at this point we have the remainder in the high word, save it + mqs4 _working_mem8+4,7 + ; and then we left shift one more time to get the quotient + jps .div_lsl64 + azz _temp_byte1,_working_mem8+0 ; add carry bit to low byte + ; the quotient is in _working_mem8+0 + mqs4 _working_mem8+0,3 + rts +.divisor_too_large: + ; quotient = 0, remander = dividend + ms4q 3,_argX4 ; get dividend + mqs4 _argX4,7 ; set dividend to remainder + clq _argX4 ; set quotient to 0 + mqs4 _argX4,3 ; set quotient + rts +.divide_by_zero: + ; for now, just return 0 + mqs4 _argY4,7 ; _argY4 is already 0 +.return_zero: + mqs4 _argY4,3 ; _argY4 is already 0 + rts +; .div_lsl64 +; +; local method for shifting _working_mem8 left 1 bit +.div_lsl64: + llz _working_mem8+0 + rlz _working_mem8+1 + rlz _working_mem8+2 + rlz _working_mem8+3 + rlz _working_mem8+4 + rlz _working_mem8+5 + rlz _working_mem8+6 + rlz _working_mem8+7 + rts + + + +; subtracts 4 byte value in _argY4 from 4 byte value in _argX4 +; X - Y +; +; Arguments +; X - _argX4 +; Y - _argY4 +; +; Return Value +; _argX4 = X - Y +_subtract32: + svv _argY4+0,_argX4+0 + ldz _argY4+2 sc.z _argX4+2 + ldz _argY4+3 sc.z _argX4+3 + rts + + +; _print_working_memory +; prints the contents of the _working_mem8 8 bytes in hex +; used for debugging purposes. +_print_working_memory: + jps _Print "work mem = $" + ldz _working_mem8+7 jas _PrintHex + ldz _working_mem8+6 jas _PrintHex + ldz _working_mem8+5 jas _PrintHex + ldz _working_mem8+4 jas _PrintHex + jps _Print " " + ldz _working_mem8+3 jas _PrintHex + ldz _working_mem8+2 jas _PrintHex + ldz _working_mem8+1 jas _PrintHex + ldz _working_mem8+0 jas _PrintHex + jps _Print "\n" + rts diff --git a/examples/slu4-minimal-64x4/software/primes.min64x4 b/examples/slu4-minimal-64x4/software/primes.min64x4 new file mode 100644 index 0000000..2047708 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/primes.min64x4 @@ -0,0 +1,128 @@ +; Prime Number Calculator +; This program calculates prime numbers using the algorithm documented here: +; https://en.wikipedia.org/wiki/Primality_test +; +#require "slu4-min64x4-asm >= 1.1.0" + +.memzone ZERO_PAGE_APPS +#mute +_n_value: .zero 4 + + +#unmute + +.org 0 "USER_APPS" +start: + ; init N-value to 1 + miq 1,_n_value+0 +.n_loop: + phsi 0 + phsq _n_value + jps is_prime32 + pls4 + pls cpi 1 beq .print_is_prime + jpa .increment_n +.print_is_prime: + phsq _n_value jps print_uint32 pls4 + phsptr is_prime_str jps _PrintPtr pls2 + ; jps _WaitInput +.increment_n: + inq_ _n_value + jpa .n_loop + +is_prime_str: .cstr " is prime!\n" +is_not_prime_str: .cstr " is not prime\n" + +; is_prime32 +; determines wither the passed uint32 is a prime +; +; Arguments +; sp+3 : the value to determine if prime (4 bytes) +; sp+7 : a place holder for return boolean +; +; returns +; sp+7 : 0 or 1 depending on whether N is prime +is_prime32: + ; first check for 2 or 3 + lds 3 cpi 0 bne .modulo_two ; check top byte of N for 0 + lds 4 cpi 0 bne .modulo_two ; check top byte of N for 0 + lds 5 cpi 0 bne .modulo_two ; check top byte of N for 0 + lds 6 cpi 3 beq .is_prime ; check N==3 + cpi 2 beq .is_prime ; check N==2 + cpi 1 beq .is_not_prime ; check N==1 + cpi 0 beq .is_not_prime ; check N==0 +.modulo_two: + lds 6 lr1 bcc .is_not_prime ; see if N's least signficant bit is even or odd +.modulo_three: + phs4i 3 ; place divisor on stack + phs4s (3+4) ; place dividend on stack (from stack) + jps divide32 + pls4 + ; check if remainder is 0 + phs4i 0 jps compare_uint32ss pls4 + pls4 + beq .is_not_prime +.loop_init: + ; set i-value (long) to 5 + cll .current_i_val ; clear all of I + mib 5, .current_i_val+0 ; copy 5 in to LSB +.loop: + phs4a .current_i_val + phs4a .current_i_val + jps multiply_uint32 + ; high 4 bytes of result should be 0 since we are only doing 32 bit N + lds 1 cpi 0 bne .iteration_loop_done + lds 2 cpi 0 bne .iteration_loop_done + lds 3 cpi 0 bne .iteration_loop_done + lds 4 cpi 0 bne .iteration_loop_done + pls4 + ms4l 1,.isquared + pls4 + phs4a .isquared phs4s 3+4 jps compare_uint32ss pls4 pls4 + ; if I*I > N, we are done + bgt .loop_done_is_prime +.n_gte_i_squared: + ; now check various modulos. + ; check N % I == 0 + phs4a .current_i_val ; I + phs4s (3+4) ; N + jps divide32 + pls4 ; quotient + phs4i 0 + jps compare_uint32ss + pls4 + pls4 + beq .loop_done_is_not_prime + ; check N % (I+2) == 0 + mll .current_i_val,.temp_val + ail 2, .temp_val + phs4a .temp_val + phs4s (3+4) + jps divide32 + pls4 ; quotient + phs4i 0 + jps compare_uint32ss + pls4 ; zero + pls4 ; remainder + beq .loop_done_is_not_prime + ; add 6 to I and loop + ail 6, .current_i_val + jpa .loop +.iteration_loop_done: + ; get rid of I*I stack + pls4 + pls4 +.loop_done_is_prime: +.is_prime: + ldi 1 sts 7 + rts +.loop_done_is_not_prime: +.is_not_prime: + ldi 0 sts 7 + rts +.current_i_val: .4byte 0 +.temp_val: .4byte 0 +.isquared: .4byte 0 + +#include "mathlib32.min64x4" +#include "stringlib.min64x4" diff --git a/examples/slu4-minimal-64x4/software/random-maze.min64x4 b/examples/slu4-minimal-64x4/software/random-maze.min64x4 new file mode 100644 index 0000000..bf6b779 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/random-maze.min64x4 @@ -0,0 +1,31 @@ +; Random Maze +; Generates a random sequence of \ and / charcters, creating the +; appearance of a maze in the terminal. Reminiscent of the +; "10 PRINT" random maze BASIC program from the 1980's. See: +; +; https://10print.org +; +#require "slu4-min64x4-asm >= 1.1.0" + +LEFT_WALL_CHAR = $1E +RIGHT_WALL_CHAR = $1F + +.org $8000 + +init: + spinit + phsptr _start_maze_str jps _PrintPtr pls2 +start: + ; get a random 8-bit number, place in register A + jps _Random + ; see if random value in A LSB is 1 + lr1 bcs .right_wall +.left_wall: + ldi LEFT_WALL_CHAR jas _PrintChar + jpa .end_loop +.right_wall: + ldi RIGHT_WALL_CHAR jas _PrintChar +.end_loop: + jpa start + +_start_maze_str: .cstr 'Random Maze!\n' diff --git a/examples/slu4-minimal-64x4/software/stars.min64x4 b/examples/slu4-minimal-64x4/software/stars.min64x4 new file mode 100644 index 0000000..02ea015 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/stars.min64x4 @@ -0,0 +1,124 @@ +; --------------------------------------------------------------------------- +; Startfield Simulation Demo by Carsten Herting (slu4) 2024 +; Simulates a starfield of 1000 stars moving towards the viewer by using +; the projection sx = x * 256 / z and sy = y * 256 / z, where (sy, sy) +; is the screen coordinate, and (x,y,z) is the position of a star. +; The number 256 represents the distance of the observer from the background. +; --------------------------------------------------------------------------- +; +; Code modified by Michael Kamprath (michaelkampath) 2024 +; Adapted to be compiled with BespokeASM +; +#require "slu4-min64x4-asm >= 1.1.0" + +.org 0 "USER_APPS" + JPS MakeStars ; generate star data + JPS _Clear + +drawloop: JPS UpdateStars FPA drawloop ; move and draw the stars in an endless loop + +; ------------------------------------------------------------------------------ +; Generate N random star elements (x,y,z) at STARDATA: quadinfo, x, y, z, sx, sy +; modifies: z0 +; ------------------------------------------------------------------------------ +MakeStars: MIV 0x00f0,z0 ; make N stars + MIV STARDATA,ptr ; start of star data table + makestar: JPS _Random ANI 3 STT ptr INV ptr ; quadrant of star (0, 1, 2, 3) + redox: JPS _Random CPI 200 FCS redox STT ptr INV ptr ; unsigned x + redoy: JPS _Random ANI 0x7f CPI 120 FCS redoy STT ptr INV ptr ; unsigned y + redoz: JPS _Random STT ptr INV ptr ; unsigned z + MIT 0,ptr INV ptr MIT 0,ptr INV ptr ; unsigned sx=0, unsigned sy=0 + DEV z0 FCS makestar + MIT 0xff,ptr ; write endmarker + RTS + +; ------------------------------------------------------------------------- +; Fast unsigned division of 256 * (8-bit A) / (8-bit B) = (16-bit C). The +; remainder is in inta+1. +; 1. A is shifted up one step into inta+1 +; 2. If B fits into inta+1, B is subtracted and C is incremented. +; 3. Step 1 and 2 are repeated 16 times, with each loop-back shifting up C. +; ------------------------------------------------------------------------- +Div256x8_8: CLZ inta+1 CLV intc ; set A MSB = 0, result C = 0 plus endmarker + MIZ 16,cnt FPA ds_entry + ds_next: LLV intc ; shift up existing result bits + ds_entry: LLV inta FCS ds_always FEQ ds_never ; shift up A into empty MSB, watch out for carry + SUZ intb FCC ds_never ; CASE C=0: A >= B? + STZ inta+1 INZ intc+0 ; subtract B from A_MSB, set bit 0 of C + ds_never: DEZ cnt FGT ds_next + RTS + ds_always: SZZ intb,inta+1 INZ intc ; CASE C=1: A > B! + DEZ cnt FGT ds_next + RTS + +; ------------------------------------------------ +; deletes a star at position (xa, ya) of sector z0 +; ------------------------------------------------ +DeleteStar: CLZ xa+1 + LDZ z0 CPI 0 FEQ dsector0 + CPI 1 FEQ dsector1 + CPI 2 FEQ dsector2 + dsector3: NEV xa AIV 200,xa AIZ 120,ya JPS _ClearPixel RTS + dsector2: NEV xa AIV 200,xa NEZ ya AIZ 120,ya JPS _ClearPixel RTS + dsector1: AIV 200,xa NEZ ya AIZ 120,ya JPS _ClearPixel RTS + dsector0: AIV 200,xa AIZ 120,ya JPS _ClearPixel RTS + +; ------------------------------------------------ +; draws a star at position (z1, intc) of sector z0 +; ------------------------------------------------ +DrawStar: MZZ z1,xa+0 CLZ xa+1 MZZ intc+0,ya + LDZ z0 CPI 0 FEQ sector0 + CPI 1 FEQ sector1 + CPI 2 FEQ sector2 + sector3: NEV xa AIV 200,xa AIZ 120,ya JPS _SetPixel RTS + sector2: NEV xa AIV 200,xa NEZ ya AIZ 120,ya JPS _SetPixel RTS + sector1: AIV 200,xa NEZ ya AIZ 120,ya JPS _SetPixel RTS + sector0: AIV 200,xa AIZ 120,ya JPS _SetPixel RTS + +.align + +UpdateStars: MIV STARDATA,ptr + updateloop: LDT ptr CPI 0xff FNE nextstar + RTS + nextstar: STZ z0 ; quad -> z0 + AIV 4,ptr LDT ptr STZ xa ; old screen position to (xa,ya) for deletion + INV ptr LDT ptr STZ ya + SIV 4,ptr LDT ptr STZ inta+0 ; x -> inta + AIV 2,ptr SIT 1,ptr FNE zinside ; z: move star towards observer + xoutside: DEV ptr ; respawn star far away, move down from z -> y + youtside: DEV ptr ; move down from y -> x + JPS DeleteStar + redox2: JPS _Random CPI 200 FCS redox2 STT ptr INV ptr ; new unsigned x + redoy2: JPS _Random ANI 0x7f CPI 120 FCS redoy2 STT ptr ; new unsigned y + unchanged: AIV 4,ptr FPA updateloop ; goto start of next star element + + zinside: STZ intb ; z -> intb + JPS Div256x8_8 ; nsx = 256 * x / z + CIZ 0,intc+1 FNE xoutside ; nsx outside viewport? + CIZ 200,intc+0 FCS xoutside + MZZ intc+0,z1 ; nsx -> z1 + DEV ptr LDT ptr STZ inta ; abs(y) -> inta, z in intb remains unchanged + JPS Div256x8_8 ; nsy = 256 * y / z + CIZ 0,intc+1 FNE youtside ; nsy outside viewport? + CIZ 120,intc+0 FCS youtside ; nsy -> intc+0 + CZZ z1,xa+0 FNE changed + CZZ intc+0,ya FEQ unchanged + changed: AIV 2,ptr ; goto sx + LDZ z1 STT ptr INV ptr ; update screen coordinates (sx, sy) + LDZ intc+0 STT ptr INV ptr ; ptr now points to next element + JPS DeleteStar JPS DrawStar ; update star on screen + FPA updateloop + +STARDATA: ; put star data here + +#mute +.org 0 "ZERO_PAGE_OS" ; zero-page variables and constants +xa: .2byte 0xffff ; MinOS graphics interface (_SetPixel, _ClearPixel) +ya: .byte 0xff +inta: .2byte 0xffff ; math registers +intb: .byte 0xff ; calculates 0x0300 / 0x96 = 0x05 R 0x12 +intc: .2byte 0xffff ; math result register +cnt: .byte 0xff ; math bit counter +ptr: .2byte 0xffff ; star pointer +z0: .byte 0xff ; multi-purpose registers +z1: .byte 0xff diff --git a/examples/slu4-minimal-64x4/software/stringlib.min64x4 b/examples/slu4-minimal-64x4/software/stringlib.min64x4 new file mode 100644 index 0000000..a2134b8 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/stringlib.min64x4 @@ -0,0 +1,193 @@ +; String Library +; various routines for manipulating strings +; +#require "slu4-min64x4-asm >= 1.1.0" +; Zero Page Usage +.memzone ZERO_PAGE_APPS +#mute +_counter: .byte 0 +_digit_counter: .byte 0 +_carry_bit: .byte 0 +_tmp_Ptr1: .2byte 0 +_tmp_Ptr2: .2byte 0 +_working_mem5: .zero 5 + +#emit +.memzone USER_APPS + +; cstr_reverse +; Reverses the ordering of all characters in the cstr. +; +; Arguments +; sp+3 : buffer address to cstr. Should be mutable, and less than 255 characters. +; +; Returns +; sp+3 : The contents of the buffer are reverse. +; +; TODO - check to see if there is enough rroom in teh stack to do this operation. +; +.align +cstr_reverse: + lds 3+0 stz _tmp_Ptr1+1 + lds 3+1 stz _tmp_Ptr1+0 + mvv _tmp_Ptr1,_tmp_Ptr2 + clz _counter +.push_loop: + cit 0,_tmp_Ptr1 feq .init_pop_loop + ldt _tmp_Ptr1 phs + inz _counter + inv _tmp_Ptr1 + fpa .push_loop +.init_pop_loop: +.pop_loop: + ciz 0,_counter feq .end + pls stt _tmp_Ptr2 + inv _tmp_Ptr2 + dez _counter + fpa .pop_loop +.end: + rts + + +; uint32_to_decimal_cstr +; +; converts the passed uint32 value to a decimal formatted cstr. +; +; Arguments +; sp+3 : the uint32 value (4 byte) +; sp+7 : buffer address (2 bytes) +; sp+9 : buffer size (1 byte) +; +; Returns +; writes binary string to buffer. Will reset all other values in buffer to null (0) +; updates sp+9 top the character length of the decimal string +; resets sp+7 to the buffer address that the null char was written to +; + +uint32_to_decimal_cstr: + ; set up working stack: + ; little endian + ; 0 : low word (4 bytes) --> value + ; 4 : high word (4 bytes) --> becomes remainder + ; init carry bit + clz _carry_bit + ; init working memory + ms4q 3,_working_mem5+0 + clz _working_mem5+4 + ; init digit counter + miz 0,_digit_counter + ; init buffer pointer + ms2v 7,_tmp_Ptr1 +.outer_loop: + ; first check to see if we are done + ciz 0,_working_mem5+3 bne .greater_than_10 + ciz 0,_working_mem5+2 bne .greater_than_10 + ciz 0,_working_mem5+1 bne .greater_than_10 + ciz 9,_working_mem5+0 ble .last_digit +.greater_than_10: +.outer_loop_continue: + miz 32,_counter ; init loop counter +.div_loop: + ; shift working stack left 1 bit and add in carry bit + jps .div_lsl40 + azz _carry_bit,_working_mem5+0 + clz _carry_bit + ; check to see if upper word by is >= 10. Assume high word won't be >255. + ; if 10 is subtractable from high byte, proceed with subtraction, otherwise continue loop + ciz 9,_working_mem5+4 ble .div_loop_continue +.div_loop_subtraction: + ; just need to subtract one byte + siz 10,_working_mem5+4 + ; set carry bit flag + miz 1,_carry_bit +.div_loop_continue: + dez _counter + ciz 0,_counter bne .div_loop +.div_loop_remainder: + ; we are done with this digit. High byte is the /10 remainder, or the current digit + ; get character of remainder by adding value to '0' ($30) + ldz _working_mem5+4 adi $30 + ; store character in buffer + stt _tmp_Ptr1 ; set next buffer position to character + ; do one more rotation before next loop + jps .div_lsl40 + azz _carry_bit,_working_mem5+0 + clz _carry_bit ; clear carry bit + clz _working_mem5+4 ; clear remainder + ; prepare for next noop + inz _digit_counter + ; no 32-bit number will be greater than 10 digits. check for error. + ciz 10,_digit_counter bgt .err_buffer_size + ; prep for next loop + inv _tmp_Ptr1 + jpa .outer_loop +.last_digit: + ; get character of remainder + ldz _working_mem5+0 adi $30 + ; store character in buffer + stt _tmp_Ptr1 + inz _digit_counter + ; no 32-bit number will be greater than 10 digits. check for error. + ciz 10,_digit_counter bgt .err_buffer_size + ; set null character + inv _tmp_Ptr1 + mit 0,_tmp_Ptr1 + ; save charastring endcter position to stack + ldz _tmp_Ptr1+0 phs + ldz _tmp_Ptr1+1 phs + ; finally need to reverse digits + phs2s 7+2 ; get original buffer address + jps cstr_reverse + pls2 + ; write string end position to return buffer address + ms2v 1,_tmp_Ptr1 + pls2 + mvs2 _tmp_Ptr1,7 + ldz _digit_counter sts 9 ; move character count to return size + rts +; .div_lsl40 +; +; local method for shifting _working_mem5 left 1 bit +.div_lsl40: + llz _working_mem5+0 + rlz _working_mem5+1 + rlz _working_mem5+2 + rlz _working_mem5+3 + rlz _working_mem5+4 + rts +.err_buffer_size: + ; print error message + jps _Print "\nERROR: 32-bit digit conversion overflowed\n\n" + jpa _Prompt + + +; print_uint32 +; prints the passed unsigned 4 byte integer in decimal format +; +; Arguments: +; sp + 3 : 32-bit value to print (4 bytes) big endian +; +BUFFER_SIZE = 32 +print_uint32: + phsi BUFFER_SIZE + phsptr .buffer + phs4s (3+3) + jps uint32_to_decimal_cstr + pls4 + pls2 + pls + phs2i .buffer jps _PrintPtr pls2 + rts +.buffer: .zero BUFFER_SIZE + + +_print_working_memory: + jps _Print "working mem = " + ldz _working_mem5+4 jas _PrintHex + jps _Print " " + ldz _working_mem5+3 jas _PrintHex + ldz _working_mem5+2 jas _PrintHex + ldz _working_mem5+1 jas _PrintHex + ldz _working_mem5+0 jas _PrintHex + jps _Print "\n" + rts diff --git a/examples/slu4-minimal-64x4/software/vga.min64x4 b/examples/slu4-minimal-64x4/software/vga.min64x4 new file mode 100644 index 0000000..10915c4 --- /dev/null +++ b/examples/slu4-minimal-64x4/software/vga.min64x4 @@ -0,0 +1,156 @@ +; VGA Demo for Minimal 64x4 Home Computer by Carsten Herting (slu4) 2024 +; +; Code modified by Michael Kamprath (michaelkampath) 2024 +; Adapted to be compiled with BespokeASM +#require "slu4-min64x4-asm >= 1.1.0" + +ViewPort = 0x430c + +.org 0 "USER_APPS" + MIB 0xfe,0xffff ; initialize stack + + CLV _XPos ; reset cursor position + reset: MIZ ' ',0 + again: INZ 0 FCS reset ; print 29 rows displaying chars 32-255 + JAS _PrintChar + LDZ _YPos CPI 29 FCC weiter + CLZ _YPos + weiter: JPS _ReadInput CPI 0 FEQ again + +loopcl: LDI 0xff JAS VGA_Fill ; fill the screen + JPS _WaitInput + JPS _Clear + +loop1: JPS _Random ANI 63 CPI 50 FCS loop1 STZ _XPos ; print hello world + JPS _Random ANI 31 CPI 28 FGT loop1 STZ _YPos + FNE printit + LDZ _XPos CPI 50-12 FCS loop1 + printit: JPS _Print "Hello, world!" + JPS _ReadInput CPI 0 FEQ loop1 + JPS _Clear + +loop2: JPS _Random CPI 200 FCS loop2 ; plot pixels at random positions + LL1 STZ 0x80 LDI 0 RL1 STZ 0x81 + JPS _Random ANI 1 OR.Z 0x80 + loop70: JPS _Random CPI 240 FCS loop70 STZ 0x82 + JPS _SetPixel + JPS _ReadInput CPI 0 FEQ loop2 + JPS _Clear + +loop4: JPS _Random STZ 0x80 CLZ 0x81 ; draw random rectangles at x: 0-255, y:0-127 + JPS _Random LR1 STZ 0x82 + MIZ 144,0x83 CLZ 0x84 MIZ 112,0x85 + JPS _ScanPS2 + JPS _Rect + JPS _ReadInput CPI 0 FEQ loop4 + JPS _Clear + +loop3: JPS _Random CPI 200 FCS loop3 ; draw random lines + LL1 STZ 0x80 LDI 0 RL1 STZ 0x81 + JPS _Random ANI 1 OR.Z 0x80 + loop71: JPS _Random CPI 240 FCS loop71 STZ 0x82 + loop72: JPS _Random CPI 200 FCS loop72 + LL1 STZ 0x83 LDI 0 RL1 STZ 0x84 + JPS _Random ANI 1 OR.Z 0x83 + loop6: JPS _Random CPI 240 FCS loop6 STZ 0x85 + JPS _ScanPS2 + JPS _Line + JPS _ReadInput CPI 0 BEQ loop3 + + JPS _Clear + MIV sprite1,sptr MIZ 16,sh + +redox: JPS _Random CPI 192 FCS redox ; should be 0...384 but is 0..383 + LL1 STZ sx+0 LDI 0 RL1 STZ sx+1 + JPS _Random ANI 1 OR.Z sx+0 + redoy: JPS _Random CPI 224 FGT redoy STZ sy ; 0..224 (= 240 - 16) + JPS DelRect + JPS DrawSprite + JPS _ReadInput CPI 0 FEQ redox + + CLZ _XPos MIZ 29,_YPos ; set cusor to bottom left + JPA _Prompt ; and exit + +; ---------------------------------------------------------------------------------- +; Deletes a 16x16 pixel rectangle at pixel position of left upper corner at (x|y) +; 0x80..81: x-position +; 0x82: y-position +; 0x83: rect height +; modifies: 0x00, mask +; ---------------------------------------------------------------------------------- +DelRect: MIV 0x00ff,mask+0 ; prepare the erase mask 0xff0000ff + MIV 0xff00,mask+2 + LDZ sy LL6 STZ vadr+0 ; LSB of ypos*64 + LDZ sy RL7 ANI 63 ADI BYTE1(ViewPort) STZ vadr+1 ; MSB of ypos*64 (rotate via C) + LDZ sx+1 DEC LDZ sx+0 RL6 ANI 63 ; add xpos/8 + ADI LSB(ViewPort) OR.Z vadr+0 + LDZ sx ANI 7 DEC FCC maskdone ; store sub byte pixel pos + STZ 0 + maskloop: LLQ mask DEZ 0 FCS maskloop ; shift mask once to pixel position + maskdone: MZZ sh,0 ; number of lines to process + lineloop: LDT vadr ANZ mask+1 STT vadr INZ vadr+0 + LDT vadr ANZ mask+2 STT vadr INZ vadr+0 + LDT vadr ANZ mask+3 STT vadr AIV 62,vadr ; goto next line + DEZ 0 FGT lineloop + RTS + +; ---------------------------------------------------------------------------------- +; Draws a 16x16 pixel sprite at pixel position (x|y) +; 0x80..81: x-position sx +; 0x82: y-position sy +; 0x83: sprite height sh +; 0x84..85: sptr sprite data pointer +; modifies: 0x00, 0x01, dptr, sbuf, shift, vadr +; ---------------------------------------------------------------------------------- +DrawSprite: LDZ sy LL6 STZ vadr+0 ; LSB of ypos*64 + LDZ sy RL7 ANI 63 ADI BYTE1(ViewPort) STZ vadr+1 ; MSB of ypos*64 (rotate via C) + LDZ sx+1 DEC LDZ sx+0 RL6 ANI 63 ; add xpos/8 + ADI LSB(ViewPort) OR.Z vadr+0 + LDZ sx ANI 7 STZ shift ; store sub byte pixel pos + MZZ sh,1 ; number of lines to process + MVV sptr,dptr ; copy the sprite data pointer + slinloop: LDT dptr STZ sbuf+0 INV dptr ; shift sprite bits + LDT dptr STZ sbuf+1 INV dptr + CLZ sbuf+2 + LDZ shift DEC FCC shiftdone ; shift that buffer to pixel position + STZ 0 + shiftloop: LLQ sbuf DEZ 0 FCS shiftloop + shiftdone: LDT vadr ORZ sbuf+0 STT vadr INZ vadr+0 + LDT vadr ORZ sbuf+1 STT vadr INZ vadr+0 + LDT vadr ORZ sbuf+2 STT vadr AIV 62,vadr + DEZ 1 FGT slinloop ; haben wir alle sprite lines verarbeitet? + RTS + +sprite1: + .byte 0x00,0x00,0x00,0x04,0x00,0x0b,0xc0,0x08,0x30,0x10,0x0c,0x10,0x02,0x20,0x04,0x20 + .byte 0x04,0x40,0x08,0x40,0x08,0x80,0x10,0x60,0x10,0x18,0x20,0x06,0xa0,0x01,0x40,0x00 + +sprite2: + .byte 0x00,0x00,0x00,0x04,0x00,0x0f,0xc0,0x0f,0xf0,0x1f,0xfc,0x1f,0xfe,0x3f,0xfc,0x3f, + .byte 0xfc,0x7f,0xf8,0x7f,0xf8,0xff,0xf0,0x7f,0xf0,0x1f,0xe0,0x07,0xe0,0x01,0x40,0x00, + +; ******************************************************************************* +; Fills pixel area with value in A +; ******************************************************************************* +VGA_Fill: STB vf_loopx+1 ; save fill value + MIW ViewPort,vf_loopx+2 ; init VRAM pointer + MIZ 240,1 ; number of lines + vf_loopy: MIZ 50,0 ; number of cols + vf_loopx: MIB 0xcc,0xcccc INB vf_loopx+2 + DEZ 0 FNE vf_loopx ; self-modifying code + AIW 14,vf_loopx+2 ; add blank cols + DEZ 1 FNE vf_loopy + RTS + +#mute +.org 0 "ZERO_PAGE_OS" + +sx: .2byte 0xffff ; sprite engine +sy: .byte 0xff +sh: .byte 0xff ; number of sprite lines to process +sptr: .2byte 0xffff +dptr: .2byte 0xffff ; sprite data pointer +vadr: .2byte 0xffff ; vram address to write to +sbuf: .byte 0, 0, 0, 0xff ; generate the shifted sprite pattern +shift: .byte 0xff ; number of pixels to shift +mask: .byte 0xff, 0, 0, 0xff ; generate the shifted delete pattern diff --git a/examples/slu4-minimal-cpu/mathlib.min-asm b/examples/slu4-minimal-cpu/mathlib.min-asm index 74ddb9d..c9d946d 100644 --- a/examples/slu4-minimal-cpu/mathlib.min-asm +++ b/examples/slu4-minimal-cpu/mathlib.min-asm @@ -261,21 +261,21 @@ subtract32: ; ; 8-bit Random Number Generator -; +; ; Based on algorithm listed here: ; https://www.electro-tech-online.com/threads/ultra-fast-pseudorandom-number-generator-for-8-bit.124249/ -; +; ; init_random8: -; +; ; Arguments ; sp+3 : A value (1 byte) ; sp+4 : B value (1 byte) ; sp+5 : C value (1 byte) -; +; ; Returns ; nothing -; +; init_random8: pushs 3 pusha _random_seed_a @@ -295,13 +295,13 @@ init_random8: rts ; random8 -; +; ; Arguments ; sp+3 - Place holder for return value -; +; ; Returns ; sp+3 - 8 bit random value -; +; random8: ; x++ ldi 1 adb _random_seed_x diff --git a/examples/slu4-minimal-cpu/random-maze.min-asm b/examples/slu4-minimal-cpu/random-maze.min-asm index 21342bc..b4300fe 100644 --- a/examples/slu4-minimal-cpu/random-maze.min-asm +++ b/examples/slu4-minimal-cpu/random-maze.min-asm @@ -1,13 +1,13 @@ ; Random Maze ; Generates a random sequence of \ and / charcters, creating the -; appearance of a maze in the terminal. Reminiscent of the +; appearance of a maze in the terminal. Reminiscent of the ; "10 PRINT" random maze BASIC program from the 1980's. See: -; +; ; https://10print.org -; +; ; Depending on the font used by your terminal program, this may or may not ; look cool. -; +; #require "slu4-mincpu-asm >= 1.5.0" @@ -44,4 +44,4 @@ start: _char_buffer: .zero 2 _start_maze_str: .cstr 'Random Maze!\n' -#include "mathlib.min-asm" \ No newline at end of file +#include "mathlib.min-asm" diff --git a/examples/slu4-minimal-cpu/random_stars.min-asm b/examples/slu4-minimal-cpu/random_stars.min-asm index 6195ffd..379f6dd 100644 --- a/examples/slu4-minimal-cpu/random_stars.min-asm +++ b/examples/slu4-minimal-cpu/random_stars.min-asm @@ -28,4 +28,4 @@ maze: JPA maze scrtxt: - .byte 27, $5b, $48, 27, $5b, $4A, 0 \ No newline at end of file + .byte 27, $5b, $48, 27, $5b, $4A, 0 diff --git a/pyproject.toml b/pyproject.toml index 587aa80..b9cabc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bespokeasm" -version = "0.4.1" +version = "0.4.2" authors = [ { name="Michael Kamprath", email="michael@kamprath.net" }, ] diff --git a/src/bespokeasm/__init__.py b/src/bespokeasm/__init__.py index c063809..2955eaf 100644 --- a/src/bespokeasm/__init__.py +++ b/src/bespokeasm/__init__.py @@ -1,4 +1,4 @@ -BESPOKEASM_VERSION_STR = "0.4.1" +BESPOKEASM_VERSION_STR = '0.4.2' # if a cconfig file requires a certain bespoke ASM version, it should be at least this version. -BESPOKEASM_MIN_REQUIRED_STR = "0.3.0" +BESPOKEASM_MIN_REQUIRED_STR = '0.3.0' diff --git a/src/bespokeasm/__main__.py b/src/bespokeasm/__main__.py index 3c38049..4d94a80 100644 --- a/src/bespokeasm/__main__.py +++ b/src/bespokeasm/__main__.py @@ -119,21 +119,21 @@ def generate_extension(): @click.option('--verbose', '-v', count=True, help='Verbosity of logging') @click.option( '--editor-config-dir', '-d', default='~/.vscode/', - help="The file path the Visual Studo Code configuration directory containing the extensions directory." + help='The file path the Visual Studo Code configuration directory containing the extensions directory.' ) @click.option( '--language-name', '-l', - help="The name of the language in the Visual Studio Code configuration file. Defaults to value " - "provide in instruction set configuration file." + help='The name of the language in the Visual Studio Code configuration file. Defaults to value ' + 'provide in instruction set configuration file.' ) @click.option( '--language-version', '-k', - help="The version of the language in the Visual Studio Code configuration file. Defaults to " - "value provide in instruction set configuration file." + help='The version of the language in the Visual Studio Code configuration file. Defaults to ' + 'value provide in instruction set configuration file.' ) @click.option( '--code-extension', '-x', - help="The file extension for asssembly code files for this language configuraton." + help='The file extension for asssembly code files for this language configuraton.' ) def vscode(config_file, verbose, editor_config_dir, language_name, language_version, code_extension): config_file = os.path.abspath(os.path.expanduser(config_file)) @@ -150,21 +150,21 @@ def vscode(config_file, verbose, editor_config_dir, language_name, language_vers @click.option('--verbose', '-v', count=True, help='Verbosity of logging') @click.option( '--editor-config-dir', '-d', default='~/', - help="The directory into which the generated configuration file should be saved." + help='The directory into which the generated configuration file should be saved.' ) @click.option( '--language-name', '-l', - help="The name of the language in the Sublime configuration file. Defaults to value " - "provide in instruction set configuration file." + help='The name of the language in the Sublime configuration file. Defaults to value ' + 'provide in instruction set configuration file.' ) @click.option( '--language-version', '-k', - help="The version of the language in the Sublime configuration file. Defaults to value " - "provide in instruction set configuration file." + help='The version of the language in the Sublime configuration file. Defaults to value ' + 'provide in instruction set configuration file.' ) @click.option( '--code-extension', '-x', - help="The file extension for asssembly code files for this language configuraton." + help='The file extension for asssembly code files for this language configuraton.' ) def sublime(config_file, verbose, editor_config_dir, language_name, language_version, code_extension): config_file = os.path.abspath(os.path.expanduser(config_file)) @@ -175,8 +175,8 @@ def sublime(config_file, verbose, editor_config_dir, language_name, language_ver def entry_point(): args = sys.argv - if "--help" in args or len(args) == 1: - click.echo("bespokeasm") + if '--help' in args or len(args) == 1: + click.echo('bespokeasm') main(auto_envvar_prefix='BESPOKEASM') diff --git a/src/bespokeasm/assembler/assembly_file.py b/src/bespokeasm/assembler/assembly_file.py index c8c0882..9503a5b 100644 --- a/src/bespokeasm/assembler/assembly_file.py +++ b/src/bespokeasm/assembler/assembly_file.py @@ -14,7 +14,7 @@ from bespokeasm.assembler.label_scope import LabelScope, LabelScopeType from bespokeasm.assembler.line_identifier import LineIdentifier from bespokeasm.assembler.line_object import LineObject -from bespokeasm.assembler.line_object.directive_line import SetMemoryZoneLine +from bespokeasm.assembler.line_object.directive_line.factory import SetMemoryZoneLine from bespokeasm.assembler.line_object.factory import LineOjectFactory from bespokeasm.assembler.line_object.label_line import LabelLine from bespokeasm.assembler.model import AssemblerModel @@ -53,7 +53,7 @@ def load_line_objects( line_objects = [] try: - with open(self.filename, 'r') as f: + with open(self.filename) as f: assembly_files_used.add(self.filename) line_num = 0 current_scope = self.label_scope @@ -97,6 +97,7 @@ def load_line_objects( for lobj in lobj_list: if not isinstance(lobj, ConditionLine): lobj.compilable = condition_stack.currently_active(preprocessor) + lobj.is_muted = condition_stack.is_muted if lobj.compilable: if isinstance(lobj, LabelLine): diff --git a/src/bespokeasm/assembler/bytecode/assembled.py b/src/bespokeasm/assembler/bytecode/assembled.py index 1e4f250..27964ea 100644 --- a/src/bespokeasm/assembler/bytecode/assembled.py +++ b/src/bespokeasm/assembler/bytecode/assembled.py @@ -56,8 +56,8 @@ def get_bytes(self, label_scope: LabelScope, instruction_address: int, instructi ) except OverflowError as ofe: sys.exit( - f'ERROR - {self.line_id}: Value could not be converted, possibly due to ' - f'improper numeric formating - {ofe}' + f'ERROR - {self.line_id}: Value {value} could not be converted byte code, possibly due to ' + f'being to large for allotted bit size of {p.value_size} - {ofe}' ) bytes = packed_bits.get_bytes() diff --git a/src/bespokeasm/assembler/bytecode/generator/__init__.py b/src/bespokeasm/assembler/bytecode/generator/__init__.py index 0244729..53aeec5 100644 --- a/src/bespokeasm/assembler/bytecode/generator/__init__.py +++ b/src/bespokeasm/assembler/bytecode/generator/__init__.py @@ -1,5 +1,4 @@ import sys -from typing import Type from bespokeasm.assembler.bytecode.generator.instruction import InstructionBytecodeGenerator from bespokeasm.assembler.bytecode.generator.macro import MacroBytecodeGenerator @@ -24,7 +23,7 @@ def generate_bytecode_parts( operands: str, isa_model: AssemblerModel, memzone_manager: MemoryZoneManager, - parser_class: Type[InstructioParserBase], + parser_class: type[InstructioParserBase], ) -> AssembledInstruction: if isinstance(instruction, Instruction): return InstructionBytecodeGenerator.generate_bytecode_parts( diff --git a/src/bespokeasm/assembler/bytecode/generator/macro.py b/src/bespokeasm/assembler/bytecode/generator/macro.py index e641dbd..b51a4cd 100644 --- a/src/bespokeasm/assembler/bytecode/generator/macro.py +++ b/src/bespokeasm/assembler/bytecode/generator/macro.py @@ -1,5 +1,4 @@ import sys -from typing import Type from bespokeasm.assembler.line_identifier import LineIdentifier from bespokeasm.assembler.bytecode.assembled import AssembledInstruction, CompositeAssembledInstruction @@ -21,7 +20,7 @@ def generate_bytecode_parts( operands: str, isa_model: AssemblerModel, memzone_manager: MemoryZoneManager, - parser_class: Type[InstructioParserBase], + parser_class: type[InstructioParserBase], ) -> AssembledInstruction: if mnemonic != macro.mnemonic: # this shouldn't happen @@ -51,7 +50,7 @@ def generate_variant_bytecode_parts( operands: str, isa_model: AssemblerModel, memzone_manager: MemoryZoneManager, - parser_class: Type[InstructioParserBase], + parser_class: type[InstructioParserBase], ) -> AssembledInstruction: if mnemonic != variant.mnemonic: # this shouldn't happen diff --git a/src/bespokeasm/assembler/bytecode/packed_bits.py b/src/bespokeasm/assembler/bytecode/packed_bits.py index 1717749..c69523a 100644 --- a/src/bespokeasm/assembler/bytecode/packed_bits.py +++ b/src/bespokeasm/assembler/bytecode/packed_bits.py @@ -8,7 +8,7 @@ def __init__(self) -> None: self._cur_bit_idx = 7 self._bytes[0] = 0 - def append_bits(self, value, bit_size, byte_aligned, endian='big') -> None: + def append_bits(self, value: int, bit_size: int, byte_aligned: bool, endian: str = 'big') -> None: value_bytes = value.to_bytes(math.ceil(bit_size/8), byteorder=endian, signed=(value < 0)) # there is probably a more efficient way to do this, but for now this works if byte_aligned and self._cur_bit_idx < 7: diff --git a/src/bespokeasm/assembler/engine.py b/src/bespokeasm/assembler/engine.py index 2158227..6aa12b2 100644 --- a/src/bespokeasm/assembler/engine.py +++ b/src/bespokeasm/assembler/engine.py @@ -88,7 +88,24 @@ def assemble_bytecode(self): # add its label to the global scope # find base file containing directory - include_dirs = set([os.path.dirname(self._source_file)]+list(self._include_paths)) + include_dirs = [os.path.dirname(self._source_file)]+list(self._include_paths) + # Deduplicate the include directories. + # This search approach will include the last instance of a directory. + deduplicated_dirs = list() + for i in range(len(include_dirs)): + left_path = os.path.realpath(include_dirs[i]) + is_duplicate = False + for j in range(i+1, len(include_dirs)): + right_path = os.path.realpath(include_dirs[j]) + if left_path == right_path: + # these are the same directory + is_duplicate = True + break + if not is_duplicate: + deduplicated_dirs.append(left_path) + include_dirs = set(deduplicated_dirs) + if self._verbose > 1: + print(f'Source will be searched in the following include directories: {include_dirs}') asm_file = AssemblyFile(self._source_file, global_label_scope) line_obs: list[LineObject] = asm_file.load_line_objects( @@ -123,11 +140,15 @@ def assemble_bytecode(self): # Sort lines according to their assigned address. This allows for .org directives compilable_line_obs.sort(key=lambda x: x.address) max_generated_address = compilable_line_obs[-1].address - line_dict = {lobj.address: lobj for lobj in compilable_line_obs if isinstance(lobj, LineWithBytes)} + line_dict = { + lobj.address: lobj + for lobj in compilable_line_obs + if isinstance(lobj, LineWithBytes) and not lobj.is_muted + } # second pass: build the machine code and check for overlaps if self._verbose > 2: - print("\nProcessing lines:") + print('\nProcessing lines:') bytecode = bytearray() last_line = None @@ -146,13 +167,13 @@ def assemble_bytecode(self): ) last_line = lobj - # Finally generate the binaey image + # Finally generate the binary image fill_bytes = bytearray([self._binary_fill_value]) addr = self._binary_start if self._generate_binary: if self._verbose > 2: - print("\nGenerating byte code:") + print('\nGenerating byte code:') while addr <= (max_generated_address if self._binary_end is None else self._binary_end): lobj = line_dict.get(addr, None) insertion_bytes = fill_bytes @@ -161,7 +182,7 @@ def assemble_bytecode(self): if line_bytes is not None: insertion_bytes = line_bytes if self._verbose > 2: - line_bytes_str = binascii.hexlify(line_bytes, sep=' ').decode("utf-8") + line_bytes_str = binascii.hexlify(line_bytes, sep=' ').decode('utf-8') click.echo(f'Address ${addr:x} : {lobj} bytes = {line_bytes_str}') bytecode.extend(insertion_bytes) addr += len(insertion_bytes) diff --git a/src/bespokeasm/assembler/keywords.py b/src/bespokeasm/assembler/keywords.py index af7e452..e2d132c 100644 --- a/src/bespokeasm/assembler/keywords.py +++ b/src/bespokeasm/assembler/keywords.py @@ -1,16 +1,17 @@ -COMPILER_DIRECTIVES_SET = set([ - 'org', 'memzone', -]) +COMPILER_DIRECTIVES_SET = { + 'org', 'memzone', 'align', +} -BYTECODE_DIRECTIVES_SET = set([ +BYTECODE_DIRECTIVES_SET = { 'fill', 'zero', 'zerountil', 'byte', '2byte', '4byte', '8byte', 'cstr', 'asciiz', -]) +} -PREPROCESSOR_DIRECTIVES_SET = set([ +PREPROCESSOR_DIRECTIVES_SET = { 'include', 'require', 'create_memzone', 'define', 'if', 'elif', 'else', 'endif', 'ifdef', 'ifndef', -]) + 'mute', 'unmute', 'emit', +} EXPRESSION_FUNCTIONS_SET = set([ 'LSB', diff --git a/src/bespokeasm/assembler/line_identifier.py b/src/bespokeasm/assembler/line_identifier.py index ad94450..e1a0ff1 100644 --- a/src/bespokeasm/assembler/line_identifier.py +++ b/src/bespokeasm/assembler/line_identifier.py @@ -1,4 +1,3 @@ - class LineIdentifier: def __init__(self, line_num: int, filename: str = None) -> None: self._filename = filename diff --git a/src/bespokeasm/assembler/line_object/__init__.py b/src/bespokeasm/assembler/line_object/__init__.py index b02940e..e9abf09 100644 --- a/src/bespokeasm/assembler/line_object/__init__.py +++ b/src/bespokeasm/assembler/line_object/__init__.py @@ -4,7 +4,7 @@ from bespokeasm.expression import EXPRESSION_PARTS_PATTERN PATTERN_LABEL_DEFINITION = r'\s*(?:\.?\w+:)' -INSTRUCTION_EXPRESSION_PATTERN = r'(?:{0}|(?:[ \t]*)(?!(?:[ \t]*\;|[ \t]*\v)|{1}))+'.format( +INSTRUCTION_EXPRESSION_PATTERN = r'(?:{}|(?:[ \t]*)(?!(?:[ \t]*\;|[ \t]*\v)|{}))+'.format( EXPRESSION_PARTS_PATTERN, PATTERN_LABEL_DEFINITION, ) @@ -19,6 +19,7 @@ def __init__(self, line_id: LineIdentifier, instruction: str, comment: str, memz self._label_scope = None self._memzone = memzone self._compilable = True + self._is_muted = False def __repr__(self): return str(self) @@ -32,15 +33,17 @@ def line_id(self) -> LineIdentifier: return self._line_id def set_start_address(self, address: int): - """Sets the address for this line object. + """Sets the initial address for this line object. If this object consists of multiple bytes, address pertains to first byte. + + The finalized address reported in the `address` property might be different from this value. """ self._address = address @property def address(self) -> int: - """Returns the address for this line object. + """Returns the finalized address for this line object. If this object consists of multiple bytes, address pertains to first byte. """ @@ -82,6 +85,19 @@ def compilable(self) -> bool: def compilable(self, value: bool): self._compilable = value + @property + def is_muted(self) -> bool: + """ + Returns True if this line object should be ignored during + emission of bytecode or certain types of pretty printing + """ + return self._is_muted + + @is_muted.setter + def is_muted(self, value: bool): + """Sets the muted state of this line object""" + self._is_muted = value + class LineWithBytes(LineObject): def __init__(self, line_id: LineIdentifier, instruction: str, comment: str, memzone: MemoryZone): diff --git a/src/bespokeasm/assembler/line_object/data_line.py b/src/bespokeasm/assembler/line_object/data_line.py index 30b113c..94253a2 100644 --- a/src/bespokeasm/assembler/line_object/data_line.py +++ b/src/bespokeasm/assembler/line_object/data_line.py @@ -57,7 +57,7 @@ def factory( elif data_match.group(3) is not None: # its a string. # first, convert escapes - converted_str = bytes(data_match.group(3), "utf-8").decode("unicode_escape") + converted_str = bytes(data_match.group(3), 'utf-8').decode('unicode_escape') values_list = [ord(x) for x in list(converted_str)] if directive_str == '.cstr' or directive_str == '.asciiz': # Add a 0-value at the end of the string values. diff --git a/src/bespokeasm/assembler/line_object/directive_line.py b/src/bespokeasm/assembler/line_object/directive_line.py deleted file mode 100644 index b675b1f..0000000 --- a/src/bespokeasm/assembler/line_object/directive_line.py +++ /dev/null @@ -1,267 +0,0 @@ -import re -import sys - -from bespokeasm.assembler.line_identifier import LineIdentifier -from bespokeasm.assembler.line_object import LineWithBytes, LineObject, INSTRUCTION_EXPRESSION_PATTERN -from bespokeasm.assembler.line_object.data_line import DataLine -from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager, GLOBAL_ZONE_NAME -from bespokeasm.expression import parse_expression, ExpressionNode -from bespokeasm.assembler.memory_zone import MEMORY_ZONE_NAME_PATTERN, MemoryZone - -# Directives are lines that tell the assembler to do something. Supported directives are: -# -# .org X - (re)sets the current address to X but does not pad. Shou;d be used at top of code. -# .byte X - Emits byte or set of bytes -# .fill X, Y - Fills X bytes from curent address with (Y&0xFF) -# .zero X - shorthand for ".fill X, 0" -# .zerountil X - fill with 0 value until and including address X - - -class DirectiveLine: - - PATTERN_ORG_DIRECTIVE = re.compile( - r'^(?:\.org)\s+({0})(?:\s*\"({1})\")?'.format( - INSTRUCTION_EXPRESSION_PATTERN, - MEMORY_ZONE_NAME_PATTERN, - ), - flags=re.IGNORECASE | re.MULTILINE - ) - - PATTERN_SET_MEMZONE_DIRECTIVE = re.compile( - r'^(?:\.memzone)\s+({0})'.format(MEMORY_ZONE_NAME_PATTERN), - flags=re.IGNORECASE | re.MULTILINE - ) - - PATTERN_FILL_DIRECTIVE = re.compile( - r'^(?:\.fill)\s+({0})\s*\,\s*({1})'.format( - INSTRUCTION_EXPRESSION_PATTERN, INSTRUCTION_EXPRESSION_PATTERN - ), - flags=re.IGNORECASE | re.MULTILINE - ) - - PATTERN_ZERO_DIRECTIVE = re.compile( - r'^(?:\.zero)\s+({0})'.format(INSTRUCTION_EXPRESSION_PATTERN), - flags=re.IGNORECASE | re.MULTILINE - ) - - PATTERN_ZEROUNTIL_DIRECTIVE = re.compile( - r'^(?:\.zerountil)\s+({0})'.format(INSTRUCTION_EXPRESSION_PATTERN), - flags=re.IGNORECASE | re.MULTILINE - ) - - def factory( - line_id: LineIdentifier, - line_str: str, - comment: str, - endian: str, - current_memzone: MemoryZone, - memzone_manager: MemoryZoneManager, - cstr_terminator: int = 0, - ) -> LineObject: - # for efficiency, if it doesn't start with a period, it is not a directive - cleaned_line_str = line_str.strip() - if not cleaned_line_str.startswith('.'): - return None - # first, the .org - line_match = re.search(DirectiveLine.PATTERN_ORG_DIRECTIVE, cleaned_line_str) - if line_match is not None and len(line_match.groups()) >= 1: - value_str = line_match.group(1) - memzone_name = line_match.group(2) - return AddressOrgLine(line_id, line_match.group(0), comment, value_str, memzone_name, memzone_manager) - - # .memzone - line_match = re.search(DirectiveLine.PATTERN_SET_MEMZONE_DIRECTIVE, cleaned_line_str) - if line_match is not None and len(line_match.groups()) == 1: - name_str = line_match.group(1) - return SetMemoryZoneLine(line_id, line_match.group(0), comment, name_str, memzone_manager) - - # .fill - line_match = re.search(DirectiveLine.PATTERN_FILL_DIRECTIVE, cleaned_line_str) - if line_match is not None and len(line_match.groups()) == 2: - count_str = line_match.group(1) - value_str = line_match.group(2) - return FillDataLine( - line_id, - line_match.group(0), - comment, - count_str, - value_str, - current_memzone, - ) - - # .zero - line_match = re.search(DirectiveLine.PATTERN_ZERO_DIRECTIVE, cleaned_line_str) - if line_match is not None and len(line_match.groups()) == 1: - count_str = line_match.group(1) - if len(count_str) == 0: - sys.exit(f'ERROR: {line_id} - .zero directive missing length argument') - return FillDataLine( - line_id, - line_match.group(0), - comment, - count_str, - '0', - current_memzone, - ) - - # .zerountil - line_match = re.search(DirectiveLine.PATTERN_ZEROUNTIL_DIRECTIVE, cleaned_line_str) - if line_match is not None and len(line_match.groups()) == 1: - address_str = line_match.group(1) - return FillUntilDataLine( - line_id, - line_match.group(0), - comment, - address_str, - '0', - current_memzone, - ) - - # nothing was matched here. pass to data directive - return DataLine.factory(line_id, line_str, comment, endian, current_memzone, cstr_terminator) - - -class SetMemoryZoneLine(LineObject): - def __init__( - self, - line_id: LineIdentifier, - instruction: str, - comment: str, - name_str: str, - memzone_manager: MemoryZoneManager, - ) -> None: - self._memzone_manager = memzone_manager - if name_str is None: - self._name = GLOBAL_ZONE_NAME - else: - self._name = name_str - memzone = memzone_manager.zone(self._name) - if memzone is None: - sys.exit(f'ERROR: {line_id} - unknown memory zone "{name_str}"') - super().__init__(line_id, instruction, comment, memzone) - - @property - def memzone_manager(self) -> MemoryZoneManager: - return self._memzone_manager - - @property - def memory_zone_name(self) -> int: - """Returns the name of the memory zone set by .memzone. - """ - return self._name - - -class AddressOrgLine(SetMemoryZoneLine): - def __init__( - self, - line_id: - LineIdentifier, - instruction: str, - comment: str, - address_expression: str, - memzone_name: str, - memzone_manager: MemoryZoneManager, - ) -> None: - super().__init__(line_id, instruction, comment, memzone_name, memzone_manager) - self._parsed_memzone_name = memzone_name - self._address_expr = parse_expression(line_id, address_expression) - - @property - def address(self) -> int: - """Returns the adjusted address value set by the .org directive. - """ - offset_value = self._address_expr.get_value(self.label_scope, self.line_id) - if self._parsed_memzone_name is None: - value = offset_value - else: - value = self.memory_zone.start + offset_value - if value < self.memzone_manager.global_zone.start: - sys.exit( - f'ERROR: {self.line_id} - .org address value of {value} is less than the minimum ' - f'address of {self.memzone_manager.global_zone.start} in memory zone {self._memzone.name}' - ) - if value > self.memzone_manager.global_zone.end: - sys.exit( - f'ERROR: {self.line_id} - .org address value of {value} is greater than the maximum ' - f'address of {self.memzone_manager.global_zone.end} in memory zone {self._memzone.name}' - ) - return value - - def set_start_address(self, address: int): - """A no-op for the .org directive - """ - return - - -class FillDataLine(LineWithBytes): - _count_expr: ExpressionNode - _value_expr: ExpressionNode - - def __init__( - self, - line_id: LineIdentifier, - instruction: str, - comment: str, - fill_count_expression: str, - fill_value_expression: str, - current_memzone: MemoryZone, - ) -> None: - super().__init__(line_id, instruction, comment, current_memzone) - self._count_expr = parse_expression(line_id, fill_count_expression) - self._value_expr = parse_expression(line_id, fill_value_expression) - self._count = None - self._value = None - - @property - def byte_size(self) -> int: - if self._count is None: - self._count = self._count_expr.get_value(self.label_scope, self.line_id) - return self._count - - def generate_bytes(self): - if self._count is None: - self._count = self._count_expr.get_value(self.label_scope, self.line_id) - if self._value is None: - self._value = self._value_expr.get_value(self.label_scope, self.line_id) - self._bytes.extend([(self._value) & 0xFF]*self._count) - - -class FillUntilDataLine(LineWithBytes): - _fill_until_addr_expr: ExpressionNode - _fill_value_expr: ExpressionNode - _fill_until_addr: int - _fill_value: int - - def __init__( - self, - line_id: LineIdentifier, - instruction: str, - comment: str, - fill_until_address_expresion: str, - fill_value_expression: str, - current_memzone: MemoryZone, - ) -> None: - super().__init__(line_id, instruction, comment, current_memzone) - self._fill_until_addr_expr = parse_expression(line_id, fill_until_address_expresion) - self._fill_value_expr = parse_expression(line_id, fill_value_expression) - self._fill_until_addr = None - self._fill_value = None - - @property - def byte_size(self) -> int: - if self._fill_until_addr is None: - self._fill_until_addr = self._fill_until_addr_expr.get_value(self.label_scope, self.line_id) - if self._fill_until_addr >= self.address: - return self._fill_until_addr - self.address + 1 - else: - return 0 - - def generate_bytes(self): - """Finalize the bytes for this fill until line. - """ - if self._fill_until_addr is None: - self._fill_until_addr = self._fill_until_addr_expr.get_value(self.label_scope, self.line_id) - if self._fill_value is None: - self._fill_value = self._fill_value_expr.get_value(self.label_scope, self.line_id) - if self.byte_size > 0 and len(self._bytes) == 0: - self._bytes.extend([self._fill_value & 0xFF]*self.byte_size) diff --git a/src/bespokeasm/assembler/line_object/directive_line/__init__.py b/src/bespokeasm/assembler/line_object/directive_line/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bespokeasm/assembler/line_object/directive_line/address.py b/src/bespokeasm/assembler/line_object/directive_line/address.py new file mode 100644 index 0000000..4e9dba7 --- /dev/null +++ b/src/bespokeasm/assembler/line_object/directive_line/address.py @@ -0,0 +1,48 @@ +import sys + +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager +from bespokeasm.expression import parse_expression, ExpressionNode +from .memzone import SetMemoryZoneLine + + +class AddressOrgLine(SetMemoryZoneLine): + def __init__( + self, + line_id: + LineIdentifier, + instruction: str, + comment: str, + address_expression: str, + memzone_name: str, + memzone_manager: MemoryZoneManager, + ) -> None: + super().__init__(line_id, instruction, comment, memzone_name, memzone_manager) + self._parsed_memzone_name = memzone_name + self._address_expr: ExpressionNode = parse_expression(line_id, address_expression) + + @property + def address(self) -> int: + """Returns the adjusted address value set by the .org directive. + """ + offset_value = self._address_expr.get_value(self.label_scope, self.line_id) + if self._parsed_memzone_name is None: + value = offset_value + else: + value = self.memory_zone.start + offset_value + if value < self.memzone_manager.global_zone.start: + sys.exit( + f'ERROR: {self.line_id} - .org address value of {value} is less than the minimum ' + f'address of {self.memzone_manager.global_zone.start} in memory zone {self._memzone.name}' + ) + if value > self.memzone_manager.global_zone.end: + sys.exit( + f'ERROR: {self.line_id} - .org address value of {value} is greater than the maximum ' + f'address of {self.memzone_manager.global_zone.end} in memory zone {self._memzone.name}' + ) + return value + + def set_start_address(self, address: int): + """A no-op for the .org directive + """ + return diff --git a/src/bespokeasm/assembler/line_object/directive_line/factory.py b/src/bespokeasm/assembler/line_object/directive_line/factory.py new file mode 100644 index 0000000..ade330e --- /dev/null +++ b/src/bespokeasm/assembler/line_object/directive_line/factory.py @@ -0,0 +1,134 @@ +import re +import sys + +from bespokeasm.assembler.model import AssemblerModel +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.line_object import LineObject, INSTRUCTION_EXPRESSION_PATTERN +from bespokeasm.assembler.line_object.data_line import DataLine +from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager +from bespokeasm.assembler.memory_zone import MEMORY_ZONE_NAME_PATTERN, MemoryZone + +from .address import AddressOrgLine +from .memzone import SetMemoryZoneLine +from .fill_data import FillDataLine, FillUntilDataLine +from .page_align import PageAlignLine + +# Directives are lines that tell the assembler to do something with respect to current address or +# generated byte code. Supported directives are: +# +# .org X - (re)sets the current address to X but does not pad. Shou;d be used at top of code. +# .byte X - Emits byte or set of bytes +# .fill X, Y - Fills X bytes from curent address with (Y&0xFF) +# .zero X - shorthand for ".fill X, 0" +# .zerountil X - fill with 0 value until and including address X +# .memzone X - sets the current memory zone to X +# .align [X] - aligns the current address to the next page boundary + + +class DirectiveLine: + + PATTERN_ORG_DIRECTIVE = re.compile( + r'^(?:\.org)\s+({})(?:\s*\"({})\")?'.format( + INSTRUCTION_EXPRESSION_PATTERN, + MEMORY_ZONE_NAME_PATTERN, + ), + flags=re.IGNORECASE | re.MULTILINE + ) + + PATTERN_SET_MEMZONE_DIRECTIVE = re.compile( + fr'^(?:\.memzone)\s+({MEMORY_ZONE_NAME_PATTERN})', + flags=re.IGNORECASE | re.MULTILINE + ) + + PATTERN_FILL_DIRECTIVE = re.compile( + r'^(?:\.fill)\s+({})\s*\,\s*({})'.format( + INSTRUCTION_EXPRESSION_PATTERN, INSTRUCTION_EXPRESSION_PATTERN + ), + flags=re.IGNORECASE | re.MULTILINE + ) + + PATTERN_ZERO_DIRECTIVE = re.compile( + fr'^(?:\.zero)\s+({INSTRUCTION_EXPRESSION_PATTERN})', + flags=re.IGNORECASE | re.MULTILINE + ) + + PATTERN_ZEROUNTIL_DIRECTIVE = re.compile( + fr'^(?:\.zerountil)\s+({INSTRUCTION_EXPRESSION_PATTERN})', + flags=re.IGNORECASE | re.MULTILINE + ) + + def factory( + line_id: LineIdentifier, + line_str: str, + comment: str, + endian: str, + current_memzone: MemoryZone, + memzone_manager: MemoryZoneManager, + isa_model: AssemblerModel, + ) -> LineObject: + # for efficiency, if it doesn't start with a period, it is not a directive + cleaned_line_str = line_str.strip() + if not cleaned_line_str.startswith('.'): + return None + # first, the .org + line_match = re.search(DirectiveLine.PATTERN_ORG_DIRECTIVE, cleaned_line_str) + if line_match is not None and len(line_match.groups()) >= 1: + value_str = line_match.group(1) + memzone_name = line_match.group(2) + return AddressOrgLine(line_id, line_match.group(0), comment, value_str, memzone_name, memzone_manager) + + # .memzone + line_match = re.search(DirectiveLine.PATTERN_SET_MEMZONE_DIRECTIVE, cleaned_line_str) + if line_match is not None and len(line_match.groups()) == 1: + name_str = line_match.group(1) + return SetMemoryZoneLine(line_id, line_match.group(0), comment, name_str, memzone_manager) + + # .fill + line_match = re.search(DirectiveLine.PATTERN_FILL_DIRECTIVE, cleaned_line_str) + if line_match is not None and len(line_match.groups()) == 2: + count_str = line_match.group(1) + value_str = line_match.group(2) + return FillDataLine( + line_id, + line_match.group(0), + comment, + count_str, + value_str, + current_memzone, + ) + + # .zero + line_match = re.search(DirectiveLine.PATTERN_ZERO_DIRECTIVE, cleaned_line_str) + if line_match is not None and len(line_match.groups()) == 1: + count_str = line_match.group(1) + if len(count_str) == 0: + sys.exit(f'ERROR: {line_id} - .zero directive missing length argument') + return FillDataLine( + line_id, + line_match.group(0), + comment, + count_str, + '0', + current_memzone, + ) + + # .zerountil + line_match = re.search(DirectiveLine.PATTERN_ZEROUNTIL_DIRECTIVE, cleaned_line_str) + if line_match is not None and len(line_match.groups()) == 1: + address_str = line_match.group(1) + return FillUntilDataLine( + line_id, + line_match.group(0), + comment, + address_str, + '0', + current_memzone, + ) + + # .page + line_match = PageAlignLine.PATTERN_PAGE_ALIGN.match(cleaned_line_str) + if line_match is not None: + return PageAlignLine(line_id, cleaned_line_str, comment, current_memzone, isa_model.page_size) + + # nothing was matched here. pass to data directive + return DataLine.factory(line_id, line_str, comment, endian, current_memzone, isa_model.cstr_terminator) diff --git a/src/bespokeasm/assembler/line_object/directive_line/fill_data.py b/src/bespokeasm/assembler/line_object/directive_line/fill_data.py new file mode 100644 index 0000000..a8ffda2 --- /dev/null +++ b/src/bespokeasm/assembler/line_object/directive_line/fill_data.py @@ -0,0 +1,78 @@ +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.line_object import LineWithBytes +from bespokeasm.expression import parse_expression, ExpressionNode +from bespokeasm.assembler.memory_zone import MemoryZone + + +class FillDataLine(LineWithBytes): + _count_expr: ExpressionNode + _value_expr: ExpressionNode + + def __init__( + self, + line_id: LineIdentifier, + instruction: str, + comment: str, + fill_count_expression: str, + fill_value_expression: str, + current_memzone: MemoryZone, + ) -> None: + super().__init__(line_id, instruction, comment, current_memzone) + self._count_expr = parse_expression(line_id, fill_count_expression) + self._value_expr = parse_expression(line_id, fill_value_expression) + self._count = None + self._value = None + + @property + def byte_size(self) -> int: + if self._count is None: + self._count = self._count_expr.get_value(self.label_scope, self.line_id) + return self._count + + def generate_bytes(self): + if self._count is None: + self._count = self._count_expr.get_value(self.label_scope, self.line_id) + if self._value is None: + self._value = self._value_expr.get_value(self.label_scope, self.line_id) + self._bytes.extend([(self._value) & 0xFF]*self._count) + + +class FillUntilDataLine(LineWithBytes): + _fill_until_addr_expr: ExpressionNode + _fill_value_expr: ExpressionNode + _fill_until_addr: int + _fill_value: int + + def __init__( + self, + line_id: LineIdentifier, + instruction: str, + comment: str, + fill_until_address_expresion: str, + fill_value_expression: str, + current_memzone: MemoryZone, + ) -> None: + super().__init__(line_id, instruction, comment, current_memzone) + self._fill_until_addr_expr = parse_expression(line_id, fill_until_address_expresion) + self._fill_value_expr = parse_expression(line_id, fill_value_expression) + self._fill_until_addr = None + self._fill_value = None + + @property + def byte_size(self) -> int: + if self._fill_until_addr is None: + self._fill_until_addr = self._fill_until_addr_expr.get_value(self.label_scope, self.line_id) + if self._fill_until_addr >= self.address: + return self._fill_until_addr - self.address + 1 + else: + return 0 + + def generate_bytes(self): + """Finalize the bytes for this fill until line. + """ + if self._fill_until_addr is None: + self._fill_until_addr = self._fill_until_addr_expr.get_value(self.label_scope, self.line_id) + if self._fill_value is None: + self._fill_value = self._fill_value_expr.get_value(self.label_scope, self.line_id) + if self.byte_size > 0 and len(self._bytes) == 0: + self._bytes.extend([self._fill_value & 0xFF]*self.byte_size) diff --git a/src/bespokeasm/assembler/line_object/directive_line/memzone.py b/src/bespokeasm/assembler/line_object/directive_line/memzone.py new file mode 100644 index 0000000..746f76a --- /dev/null +++ b/src/bespokeasm/assembler/line_object/directive_line/memzone.py @@ -0,0 +1,35 @@ +import sys + +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.line_object import LineObject +from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager, GLOBAL_ZONE_NAME + + +class SetMemoryZoneLine(LineObject): + def __init__( + self, + line_id: LineIdentifier, + instruction: str, + comment: str, + name_str: str, + memzone_manager: MemoryZoneManager, + ) -> None: + self._memzone_manager = memzone_manager + if name_str is None: + self._name = GLOBAL_ZONE_NAME + else: + self._name = name_str + memzone = memzone_manager.zone(self._name) + if memzone is None: + sys.exit(f'ERROR: {line_id} - unknown memory zone "{name_str}"') + super().__init__(line_id, instruction, comment, memzone) + + @property + def memzone_manager(self) -> MemoryZoneManager: + return self._memzone_manager + + @property + def memory_zone_name(self) -> int: + """Returns the name of the memory zone set by .memzone. + """ + return self._name diff --git a/src/bespokeasm/assembler/line_object/directive_line/page_align.py b/src/bespokeasm/assembler/line_object/directive_line/page_align.py new file mode 100644 index 0000000..20cbc52 --- /dev/null +++ b/src/bespokeasm/assembler/line_object/directive_line/page_align.py @@ -0,0 +1,60 @@ +# The page align directive allows the user to align the current memory address to the next page boundary. +# This is useful when it is important that the byte code to be generated is placed at the start of a page boundary, +# but it is not important that the code is at a sopecific address. If the byte code needs to be at a specific address, +# one would use the .org directive. +# +# The page align directive is used as follows: +# +# .page [page_size] +# +# Where the optional page_size is the size of the page in bytes. If page_size is not specified, the default page size +# configured in configuration file is used. The specific configuration is general->page_size. If the page_size is +# note spcified, a default value of 1 is used efficiently making the page align directive a no-op. +# +# Error cases: +# - If the page directive would generate an address that is beyond the currently active memory zone, an error is +# generated. +import re + +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.memory_zone import MemoryZone +from bespokeasm.assembler.line_object import LineObject, INSTRUCTION_EXPRESSION_PATTERN +from bespokeasm.expression import parse_expression, ExpressionNode + + +class PageAlignLine(LineObject): + PATTERN_PAGE_ALIGN = re.compile( + f'^\\.align(?:\\s+({INSTRUCTION_EXPRESSION_PATTERN}))?', + ) + + def __init__( + self, + line_id: LineIdentifier, + instruction: str, + comment: str, + memzone: MemoryZone, + default_page_size: int, + ): + super().__init__(line_id, instruction, comment, memzone) + define_symbol_match = re.search(PageAlignLine.PATTERN_PAGE_ALIGN, instruction) + if define_symbol_match is not None: + if define_symbol_match.group(1) is not None: + self._page_size = parse_expression(line_id, define_symbol_match.group(1)) + else: + self._page_size = default_page_size + else: + raise ValueError(f'Invalid page align directive "{instruction}"') + + def __repr__(self) -> str: + return f'PageAlignLine' + + def set_start_address(self, address: int): + """Sets the finalized address to the next page boundary. + """ + if isinstance(self._page_size, ExpressionNode): + self._page_size = self._page_size.get_value(self.label_scope, self.line_id) + + if self._page_size == 1: + self._address = address + else: + self._address = address + (self._page_size - (address % self._page_size)) diff --git a/src/bespokeasm/assembler/line_object/emdedded_string.py b/src/bespokeasm/assembler/line_object/emdedded_string.py new file mode 100644 index 0000000..50a0944 --- /dev/null +++ b/src/bespokeasm/assembler/line_object/emdedded_string.py @@ -0,0 +1,62 @@ +# an embedded string line object is a string that has its bytecode embdedded where the string is +# in the code. Essential, it is a `.cstr` line object but without the `.cstr` prefix. The +# embedded string feature must be enabled in the ISA configuration file, whether there is a +# termination character or not to be included in the bytecode. +from __future__ import annotations +import re + +from bespokeasm.assembler.line_object import LineWithBytes +from bespokeasm.assembler.memory_zone import MemoryZone +from bespokeasm.assembler.line_identifier import LineIdentifier + + +EMBEDDED_STRING_PATTERN = r'(?P[\"])((?:\\(?P=quote)|.)*)(?P=quote)' + + +class EmbeddedString(LineWithBytes): + QUOTED_STRING_PATTERN = re.compile( + rf'^{EMBEDDED_STRING_PATTERN}', + flags=re.IGNORECASE | re.MULTILINE | re.DOTALL + ) + + @classmethod + def factory( + cls, + line_id: LineIdentifier, + instruction: str, + comment: str, + current_memzone: MemoryZone, + cstr_terminator: int = 0, + ) -> EmbeddedString: + # detyermine if string starts with a quoted string + match = re.search(EmbeddedString.QUOTED_STRING_PATTERN, instruction) + if match is None or len(match.groups()) != 2: + return None + + return EmbeddedString(line_id, match.group(0), match.group(2), comment, current_memzone, cstr_terminator) + + def __init__( + self, + line_id: LineIdentifier, + instruction: str, + quoted_string: str, + comment: str, + current_memzone: MemoryZone, + cstr_terminator: int = 0, + ) -> None: + super().__init__(line_id, instruction, comment, current_memzone) + self._string_bytes = \ + [ord(x) for x in list(bytes(quoted_string, 'utf-8').decode('unicode_escape'))] \ + + [cstr_terminator] + + def __str__(self): + return f'EmbeddedString<{self.instruction}, size={self.byte_size}, chars={self._string_bytes}>' + + @property + def byte_size(self) -> int: + """Returns the number of bytes this data line will generate""" + return len(self._string_bytes) + + def generate_bytes(self) -> None: + # set the bytes + self._bytes.extend(self._string_bytes) diff --git a/src/bespokeasm/assembler/line_object/factory.py b/src/bespokeasm/assembler/line_object/factory.py index b62df7d..52c6ac8 100644 --- a/src/bespokeasm/assembler/line_object/factory.py +++ b/src/bespokeasm/assembler/line_object/factory.py @@ -6,13 +6,14 @@ from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.line_object import LineObject from bespokeasm.assembler.line_object.label_line import LabelLine -from bespokeasm.assembler.line_object.directive_line import DirectiveLine +from bespokeasm.assembler.line_object.directive_line.factory import DirectiveLine from bespokeasm.assembler.line_object.instruction_line import InstructionLine from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager from bespokeasm.assembler.memory_zone import MemoryZone from bespokeasm.assembler.preprocessor import Preprocessor from bespokeasm.assembler.line_object.preprocessor_line.factory import PreprocessorLineFactory from bespokeasm.assembler.preprocessor.condition_stack import ConditionStack +from bespokeasm.assembler.line_object.emdedded_string import EmbeddedString class LineOjectFactory: @@ -72,7 +73,7 @@ def parse_line( # parse instruction while len(instruction_str) > 0: # try label - line_obj = LabelLine.factory( + line_obj: LineObject = LabelLine.factory( line_id, instruction_str, comment_str, @@ -93,13 +94,27 @@ def parse_line( model.endian, current_memzone, memzone_manager, - model.cstr_terminator, + model, ) if line_obj is not None: line_obj_list.append(line_obj) instruction_str = instruction_str.replace(line_obj.instruction, '', 1).strip() continue + # try embedded string + if model.allow_embedded_strings: + line_obj = EmbeddedString.factory( + line_id, + instruction_str, + comment_str, + current_memzone, + model.cstr_terminator, + ) + if line_obj is not None: + line_obj_list.append(line_obj) + instruction_str = instruction_str.replace(line_obj.instruction, '', 1).strip() + continue + # try instruction line_obj = InstructionLine.factory( line_id, @@ -114,8 +129,8 @@ def parse_line( instruction_str = instruction_str.replace(line_obj.instruction, '', 1).strip() continue - # if we are here, that means nothing was matched. Shouldn't happen, but we will break none the less - break + # if we are here, that means nothing was matched. Shouldn't happen, so let's error out + sys.exit(f'ERROR: {line_id} - unknown instruction "{instruction_str.strip()}"') if len(line_obj_list) == 0: if instruction_str != '': diff --git a/src/bespokeasm/assembler/line_object/instruction_line.py b/src/bespokeasm/assembler/line_object/instruction_line.py index ee40148..55cfbb3 100644 --- a/src/bespokeasm/assembler/line_object/instruction_line.py +++ b/src/bespokeasm/assembler/line_object/instruction_line.py @@ -7,10 +7,11 @@ from bespokeasm.assembler.model.instruction_parser import InstructioParser from bespokeasm.assembler.memory_zone import MemoryZone from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager +from bespokeasm.assembler.line_object.emdedded_string import EMBEDDED_STRING_PATTERN class InstructionLine(LineWithBytes): - COMMAND_EXTRACT_PATTERN = re.compile(r'^\s*(\w+)', flags=re.IGNORECASE | re.MULTILINE) + COMMAND_EXTRACT_PATTERN = re.compile(r'^\s*(\w[\w\._]*)', flags=re.IGNORECASE | re.MULTILINE) _INSTRUCTUION_EXTRACTION_PATTERN = None @@ -26,11 +27,14 @@ def factory( # first, extract evertything up to the next extruction if InstructionLine._INSTRUCTUION_EXTRACTION_PATTERN is None: instructions_regex = '\\b' + '\\b|\\b'.join(isa_model.operation_mnemonics) + '\\b' + # replace any period characters in instructions with escaped period characters + instructions_regex = instructions_regex.replace('.', '\\.') InstructionLine._INSTRUCTUION_EXTRACTION_PATTERN = re.compile( - r'(?i)(?:((?:{0})\b.*?)(?:(?={1})|\;|$))'.format(instructions_regex, instructions_regex), + fr'(?i)^((?:{instructions_regex}).*?(?=(?:{instructions_regex})' + fr'|\s*\;|\s*$|\s*{EMBEDDED_STRING_PATTERN}))', flags=re.IGNORECASE | re.MULTILINE, ) - instruction_match = re.search(InstructionLine._INSTRUCTUION_EXTRACTION_PATTERN, line_str) + instruction_match = re.search(InstructionLine._INSTRUCTUION_EXTRACTION_PATTERN, line_str.strip()) if instruction_match is not None: instruction_str = instruction_match.group(0) diff --git a/src/bespokeasm/assembler/line_object/label_line.py b/src/bespokeasm/assembler/line_object/label_line.py index c191422..a7f80a4 100644 --- a/src/bespokeasm/assembler/line_object/label_line.py +++ b/src/bespokeasm/assembler/line_object/label_line.py @@ -15,7 +15,7 @@ class LabelLine(LineObject): flags=re.IGNORECASE | re.MULTILINE ) PATTERN_CONSTANT = re.compile( - r'^\s*(\w+)(?:\s*\=\s*|\s+EQU\s+)({0}|)'.format(INSTRUCTION_EXPRESSION_PATTERN), + fr'^\s*(\w+)(?:\s*\=\s*|\s+EQU\s+)({INSTRUCTION_EXPRESSION_PATTERN}|)', flags=re.IGNORECASE | re.MULTILINE ) diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/__init__.py b/src/bespokeasm/assembler/line_object/preprocessor_line/__init__.py index e023010..789f549 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/__init__.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/__init__.py @@ -8,4 +8,4 @@ def __init__(self, line_id: LineIdentifier, instruction: str, comment: str, memz super().__init__(line_id, instruction, comment, memzone) def __repr__(self) -> str: - return f"PreprocessorLine<{self._line_id}>" + return f'PreprocessorLine<{self._line_id}>' diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/condition_line.py b/src/bespokeasm/assembler/line_object/preprocessor_line/condition_line.py index 76e0f30..e4fd99a 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/condition_line.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/condition_line.py @@ -4,10 +4,11 @@ from bespokeasm.assembler.line_identifier import LineIdentifier from bespokeasm.assembler.memory_zone import MemoryZone import bespokeasm.assembler.preprocessor.condition as condition +from bespokeasm.assembler.preprocessor import Preprocessor from bespokeasm.assembler.preprocessor.condition_stack import ConditionStack -CONDITIONAL_LINE_PREFIX_LIST = ["#if ", "#ifdef ", "#ifndef ", "#elif ", "#else", "#endif"] +CONDITIONAL_LINE_PREFIX_LIST = ['#if ', '#ifdef ', '#ifndef ', '#elif ', '#else', '#endif', '#mute', '#emit', '#unmute'] class ConditionLine(PreprocessorLine): @@ -17,32 +18,37 @@ def __init__( instruction: str, comment: str, memzone: MemoryZone, - condition_stack: ConditionStack + preprocessor: Preprocessor, + condition_stack: ConditionStack, ): super().__init__(line_id, instruction, comment, memzone) - if instruction.startswith("#if "): + if instruction.startswith('#if '): self._condition = condition.IfPreprocessorCondition(instruction, line_id) - elif instruction.startswith("#elif "): + elif instruction.startswith('#elif '): self._condition = condition.ElifPreprocessorCondition(instruction, line_id) - elif instruction == "#else": + elif instruction == '#else': self._condition = condition.ElsePreprocessorCondition(instruction, line_id) - elif instruction == "#endif": + elif instruction == '#endif': self._condition = condition.EndifPreprocessorCondition(instruction, line_id) - elif instruction.startswith("#ifdef ") or instruction.startswith("#ifndef "): + elif instruction.startswith('#ifdef ') or instruction.startswith('#ifndef '): self._condition = condition.IfdefPreprocessorCondition(instruction, line_id) + elif instruction == '#mute': + self._condition = condition.MutePreprocessorCondition(instruction, line_id) + elif instruction == '#unmute' or instruction == '#emit': + self._condition = condition.UnmutePreprocessorCondition(instruction, line_id) else: - raise ValueError(f"Invalid condition line: {instruction}") + raise ValueError(f'Invalid condition line: {instruction}') try: - condition_stack.process_condition(self._condition) + condition_stack.process_condition(self._condition, preprocessor) except IndexError: sys.exit( f'ERROR - {line_id}: Preprocessor condition has no matching counterpart' ) def __repr__(self) -> str: - return f"ConditionLine<{self._line_id}>" + return f'ConditionLine<{self._line_id}>' @property def compilable(self) -> bool: diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/create_memzone.py b/src/bespokeasm/assembler/line_object/preprocessor_line/create_memzone.py index 89bc52b..fa3993d 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/create_memzone.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/create_memzone.py @@ -12,7 +12,7 @@ class CreateMemzoneLine(PreprocessorLine): PATTERN_CREATE_MEMORY_ZONE = re.compile( - r'#create_memzone\s+({0})\s+({1})\s+({2})'.format( + r'#create_memzone\s+({})\s+({})\s+({})'.format( MEMORY_ZONE_NAME_PATTERN, PATTERN_NUMERIC, PATTERN_NUMERIC, @@ -56,4 +56,4 @@ def __init__( sys.exit(f'ERROR: {line_id} - Syntax error when creating memory zone') def __repr__(self) -> str: - return f"CreateMemzoneLine<{self._name}: {self._start_addr} -> {self._end_addr}>" + return f'CreateMemzoneLine<{self._name}: {self._start_addr} -> {self._end_addr}>' diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/define_symbol.py b/src/bespokeasm/assembler/line_object/preprocessor_line/define_symbol.py index 3399966..21e9807 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/define_symbol.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/define_symbol.py @@ -12,7 +12,7 @@ class DefineSymbolLine(PreprocessorLine): PATTERN_DEFINE_SYMBOL = re.compile( - r'^#define\s+({0})(?:\s+(\S.*?))?\s*$'.format(SYMBOL_PATTERN), + fr'^#define\s+({SYMBOL_PATTERN})(?:\s+(\S.*?))?\s*$', ) def __init__( @@ -45,7 +45,7 @@ def __init__( sys.exit(f'ERROR - {line_id}: Invalid preprocessor symbol definition: {instruction}') def __repr__(self) -> str: - return f"DefineSymbolLine<{self._symbol}>" + return f'DefineSymbolLine<{self._symbol}>' @property def symbol(self) -> PreprocessorSymbol: diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/factory.py b/src/bespokeasm/assembler/line_object/preprocessor_line/factory.py index 9563bb6..0502047 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/factory.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/factory.py @@ -66,6 +66,7 @@ def parse_line( instruction, comment, current_memzone, + preprocessor, condition_stack, )] return [] diff --git a/src/bespokeasm/assembler/line_object/preprocessor_line/required_language.py b/src/bespokeasm/assembler/line_object/preprocessor_line/required_language.py index b1dc240..43859cf 100644 --- a/src/bespokeasm/assembler/line_object/preprocessor_line/required_language.py +++ b/src/bespokeasm/assembler/line_object/preprocessor_line/required_language.py @@ -11,7 +11,7 @@ class RequiredLanguageLine(PreprocessorLine): PATTERN_REQUIRE_LANGUAGE = re.compile( - r'\#require\s+\"([\w\-\_\.]*)(?:\s*(==|>=|<=|>|<)\s*({0}))?\"'.format(version.VERSION_PATTERN), + fr'\#require\s+\"([\w\-\_\.]*)(?:\s*(==|>=|<=|>|<)\s*({version.VERSION_PATTERN}))?\"', flags=re.IGNORECASE | re.VERBOSE ) @@ -81,14 +81,14 @@ def __init__( if log_verbosity > 1: print( f'Code file requires language "{require_match.group(1)} {require_match.group(2)} ' - f'{require_match.group(3)}". Configurate file declares language "{isa_model.isa_name} ' + f'{require_match.group(3)}". Configuration file declares language "{isa_model.isa_name} ' f'{isa_model.isa_version}"' ) elif log_verbosity > 1: print( f'Code file requires language "{require_match.group(1)}". ' - f'Configurate file declares language "{isa_model.isa_name} v{isa_model.isa_version}"' + f'Configuration file declares language "{isa_model.isa_name} v{isa_model.isa_version}"' ) def __repr__(self) -> str: - return f"RequiredLanguageLine<{self._language} {self._operator_str} {self._version_obj}>" + return f'RequiredLanguageLine<{self._language} {self._operator_str} {self._version_obj}>' diff --git a/src/bespokeasm/assembler/model/__init__.py b/src/bespokeasm/assembler/model/__init__.py index b10a125..6bc9860 100644 --- a/src/bespokeasm/assembler/model/__init__.py +++ b/src/bespokeasm/assembler/model/__init__.py @@ -45,7 +45,7 @@ def __init__(self, config_file_path: str, is_verbose: int): self._file_extension = self._config['general']['identifier'].get('extension', 'asm') # enforce semantic versioning version_match = re.match( - r"^\s*" + version.VERSION_PATTERN + r"\s*$", + r'^\s*' + version.VERSION_PATTERN + r'\s*$', self._isa_version, flags=re.IGNORECASE | re.VERBOSE, ) @@ -85,6 +85,14 @@ def _validate_config(self, is_verbose: int) -> None: 'Memory blocks have been deprecated and replaced with "data" blocks.' ) + # ensure there is a general section + if 'general' not in self._config: + sys.exit('ERROR - ISA configuration file does not contain a "general" section.') + + # what's the point if there is no instruction set? + if 'instructions' not in self._config: + sys.exit('ERROR - ISA configuration file does not contain an "instructions" section.') + # check for min required BespokeASM version if 'min_version' in self._config['general']: required_version = self._config['general']['min_version'] @@ -161,6 +169,11 @@ def address_size(self) -> int: '''The number of bits used to rerpesent a memory address''' return self._config['general']['address_size'] + @property + def page_size(self) -> int: + '''The number of bytes in a memory page''' + return self._config['general'].get('page_size', 1) + @property def registers(self) -> set[str]: return self._registers @@ -251,3 +264,7 @@ def predefined_symbols(self) -> list[dict]: return self._config['predefined']['symbols'] else: return [] + + @property + def allow_embedded_strings(self) -> bool: + return self._config['general'].get('allow_embedded_strings', False) diff --git a/src/bespokeasm/assembler/model/instruction.py b/src/bespokeasm/assembler/model/instruction.py index 8941094..c16e745 100644 --- a/src/bespokeasm/assembler/model/instruction.py +++ b/src/bespokeasm/assembler/model/instruction.py @@ -89,7 +89,7 @@ def suffix_bytecode_value(self) -> int: return 0 def __str__(self) -> str: - operand_str = str(self._operand_parser) if self._operand_parser is not None else "NO_OPERANDS" + operand_str = str(self._operand_parser) if self._operand_parser is not None else 'NO_OPERANDS' return f'InstructionVariant<{self._mnemonic, operand_str}>' diff --git a/src/bespokeasm/assembler/model/instruction_macro.py b/src/bespokeasm/assembler/model/instruction_macro.py index 8b75566..5b7efd8 100644 --- a/src/bespokeasm/assembler/model/instruction_macro.py +++ b/src/bespokeasm/assembler/model/instruction_macro.py @@ -55,7 +55,7 @@ def base_bytecode_size(self) -> int: return 0 def __str__(self) -> str: - operand_str = str(self._operand_parser) if self._operand_parser is not None else "NO_OPERANDS" + operand_str = str(self._operand_parser) if self._operand_parser is not None else 'NO_OPERANDS' return f'InstrutionMacroVariant<{self._mnemonic, operand_str}>' diff --git a/src/bespokeasm/assembler/model/instruction_parser.py b/src/bespokeasm/assembler/model/instruction_parser.py index 9f8bbff..61906bc 100644 --- a/src/bespokeasm/assembler/model/instruction_parser.py +++ b/src/bespokeasm/assembler/model/instruction_parser.py @@ -6,6 +6,7 @@ from bespokeasm.assembler.bytecode.assembled import AssembledInstruction from bespokeasm.assembler.bytecode.generator import BytecodeGenerator from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager +from bespokeasm.assembler.model.instruction_base import InstructionBase class InstructioParser(InstructioParserBase): @@ -24,9 +25,10 @@ def parse_instruction( else: operands = '' - instr_obj = isa_model.instructions.get(mnemonic) + instr_obj: InstructionBase = isa_model.instructions.get(mnemonic) if instr_obj is None: sys.exit(f'ERROR: {line_id} - Unrecognized mnemonic "{mnemonic}"') + return BytecodeGenerator.generate_bytecode_parts( instr_obj, line_id, diff --git a/src/bespokeasm/assembler/model/operand/__init__.py b/src/bespokeasm/assembler/model/operand/__init__.py index 3803efa..5585c77 100644 --- a/src/bespokeasm/assembler/model/operand/__init__.py +++ b/src/bespokeasm/assembler/model/operand/__init__.py @@ -12,7 +12,7 @@ class OperandType(enum.Enum): UNKNOWN = -1 EMPTY = 1 NUMERIC = 9 - NUMERIC_BYTECODE = 11 + NUMERIC_BYTECODE = 12 REGISTER = 8 INDEXED_REGISTER = 6 DICTIONARY_KEY = 7 @@ -20,7 +20,8 @@ class OperandType(enum.Enum): INDIRECT_INDEXED_REGISTER = 3 INDIRECT_NUMERIC = 4 DEFERRED_NUMERIC = 5 - RELATIVE_ADDRESS = 10 + ADDRESS = 10 + RELATIVE_ADDRESS = 11 class OperandBytecodePositionType(enum.Enum): @@ -123,6 +124,9 @@ def has_argument(self) -> bool: @property def argument_size(self) -> int: + """Returns the bytecode size of the argument for this operand. Must be configured.""" + if 'size' not in self._config['argument']: + sys.exit(f'ERROR: Operand {self} does not have a configured argument size') return self._config['argument']['size'] @property @@ -131,6 +135,7 @@ def argument_byte_align(self) -> bool: @property def argument_endian(self) -> str: + """Returns the endianess of the argument for this operand. Defaults to the configured default endian.""" return self._config['argument'].get('endian', self._default_endian) diff --git a/src/bespokeasm/assembler/model/operand/factory.py b/src/bespokeasm/assembler/model/operand/factory.py index c3ced29..d6dbb42 100644 --- a/src/bespokeasm/assembler/model/operand/factory.py +++ b/src/bespokeasm/assembler/model/operand/factory.py @@ -4,7 +4,7 @@ from bespokeasm.assembler.model.operand.types import empty, numeric_expression, indirect_register, \ indirect_numeric, register, indirect_indexed_register, \ deferred_numeric, numeric_bytecode, enumeration_operand, \ - numeric_enumeration, relative_address, indexed_register + numeric_enumeration, relative_address, indexed_register, address class OperandFactory: @@ -37,6 +37,8 @@ def factory(cls, operand_id: str, arg_config_dict: dict, default_endian: str, re return numeric_enumeration.NumericEnumerationOperand(operand_id, arg_config_dict, default_endian, registers) elif type_str == 'numeric_bytecode': return numeric_bytecode.NumericBytecode(operand_id, arg_config_dict, default_endian) + elif type_str == 'address': + return address.AddressOperand(operand_id, arg_config_dict, default_endian) elif type_str == 'relative_address': return relative_address.RelativeAddressOperand(operand_id, arg_config_dict, default_endian) elif type_str == 'empty': diff --git a/src/bespokeasm/assembler/model/operand/types/address.py b/src/bespokeasm/assembler/model/operand/types/address.py new file mode 100644 index 0000000..273c0c0 --- /dev/null +++ b/src/bespokeasm/assembler/model/operand/types/address.py @@ -0,0 +1,134 @@ +# The address operand type is a `NumericExpressionOperand` that enforces some conditions +# are the argument pertaining to addresses. The features this operand supports: +# - The argument value must be a valid address in a given memory zone (defaults to the global zone) +# - The bytecode of the argument value (the address) can be sliced into the least significant byte(s), +# to enable "fast jumps" in the assembler. +# * The expected significant bytes can either be configured to a specific value (e.g., "zero page") +# or to the same as the current instruction's address. If the operand's value has a signficant byte(s) +# that is not what is expected, the assembler will raise an error. +from bespokeasm.assembler.line_identifier import LineIdentifier +from bespokeasm.assembler.model.operand.types.numeric_expression import NumericExpressionOperand +from bespokeasm.assembler.memory_zone import MemoryZone +from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager, GLOBAL_ZONE_NAME +from bespokeasm.assembler.model.operand import OperandType, ParsedOperand +from bespokeasm.assembler.bytecode.parts import NumericByteCodePart, ExpressionByteCodePartInMemoryZone +from bespokeasm.assembler.label_scope import LabelScope + + +class AddressByteCodePart(ExpressionByteCodePartInMemoryZone): + def __init__( + self, + value_expression: str, + value_size: int, + byte_align: bool, + endian: str, + line_id: LineIdentifier, + memzone: MemoryZone, + is_lsb_bytes: bool, + match_address_msb: bool, + ) -> None: + """Creates a new address bytecode part that is bound to a memory zone. + The address value is sliced into the least significant bit(s) if `is_lsb_bytes` is true, + ensuring that the MSBs match the current instruction's address MSBs.""" + super().__init__(memzone, value_expression, value_size, byte_align, endian, line_id) + self._is_lsb_bytes = is_lsb_bytes + self._match_address_msb = match_address_msb + + def __str__(self) -> str: + return f'AddressByteCodePart' + + def get_value(self, label_scope: LabelScope, instruction_address: int, instruction_size: int) -> int: + if instruction_address is None: + raise ValueError('AddressByteCodePart.get_value had no instruction_address passed') + value = super().get_value(label_scope, instruction_address, instruction_size) + + if self._is_lsb_bytes and self._match_address_msb: + # mask out the MSBs of the address value. The`value_size` is interpreted as the number of LSB bytes + mask = (1 << self.value_size) - 1 + final_value = value & mask + # check if the MSBs of the value match the MSBs of the instruction address + shifted_address = instruction_address >> self.value_size + shifted_value = value >> self.value_size + if shifted_address != shifted_value: + raise ValueError( + f'Operand address value 0x{value:x} does not have the same MSBs as ' + f'the instruction address 0x{instruction_address:x} as line {self.line_id}' + ) + else: + final_value = value + return final_value + + +class AddressOperand(NumericExpressionOperand): + def __init__(self, operand_id: str, arg_config_dict: dict, default_endian: str, require_arg: bool = True) -> None: + super().__init__(operand_id, arg_config_dict, default_endian, require_arg) + + def __str__(self): + return f'AddressOperand<{self.id}>' + + @property + def type(self) -> OperandType: + return OperandType.ADDRESS + + @property + def match_pattern(self) -> str: + return r'(?:[\$\%\w\(\)\+\-\s]*[\w\)])' + + @property + def enforce_argument_valid_address(self) -> bool: + return True + + def valid_memory_zone(self, memzone_manager: MemoryZoneManager) -> MemoryZone: + """Returns the memory zone that the operand's argument must be a valid address in. Defaults + to the global zone. Raises a `ValueError` if the configured zone is not found.""" + zone_name = self.config['argument'].get('memory_zone', GLOBAL_ZONE_NAME) + zone = memzone_manager.zone(zone_name) + if zone is None: + raise ValueError(f'Invalid memory zone name "{zone_name}" for operand {self}') + return zone + + @property + def does_lsb_slice(self) -> bool: + """Returns true if the argument value should be sliced into the least significant byte(s) of + the bytecode. Defaults to false. If trues, the argument size should reflect the number of + bits to slice.""" + return self.config['argument'].get('slice_lsb', False) + + @property + def match_address_msb(self) -> bool: + """Returns true if the argument value's MSBs should match the MSBs of the current instruction's + address. Can only be true if `does_lsb_slice` is also true. Defaults to false.""" + if self.does_lsb_slice: + return self.config['argument'].get('match_address_msb', False) + return False + + def _parse_bytecode_parts( + self, + line_id: LineIdentifier, + operand: str, + register_labels: set[str], + memzone_manager: MemoryZoneManager, + ) -> ParsedOperand: + """Overrides `NumericExpressionOperand._parse_bytecode_parts` to add support for slicing ] + the least significant bytes of the argument value.""" + bytecode_part = NumericByteCodePart( + self.bytecode_value, + self.bytecode_size, + False, + 'big', + line_id + ) if self.bytecode_value is not None else None + arg_part = AddressByteCodePart( + operand, + self.argument_size, + self.argument_byte_align, + self.argument_endian, + line_id, + self.valid_memory_zone(memzone_manager), + self.does_lsb_slice, + self.match_address_msb, + ) + if arg_part.contains_register_labels(register_labels): + return None + return ParsedOperand(self, bytecode_part, arg_part, operand) diff --git a/src/bespokeasm/assembler/model/operand/types/deferred_numeric.py b/src/bespokeasm/assembler/model/operand/types/deferred_numeric.py index f175d16..ad7b68e 100644 --- a/src/bespokeasm/assembler/model/operand/types/deferred_numeric.py +++ b/src/bespokeasm/assembler/model/operand/types/deferred_numeric.py @@ -15,4 +15,4 @@ def type(self) -> OperandType: @cached_property def match_pattern(self) -> str: - return r'^\[\s*\[\s*({0})\s*\]\s*\]$'.format(super(DeferredNumericOperand.__bases__[0], self).match_pattern) + return fr'^\[\s*\[\s*({super(DeferredNumericOperand.__bases__[0], self).match_pattern})\s*\]\s*\]$' diff --git a/src/bespokeasm/assembler/model/operand/types/enumeration_operand.py b/src/bespokeasm/assembler/model/operand/types/enumeration_operand.py index 2be205b..a76fa79 100644 --- a/src/bespokeasm/assembler/model/operand/types/enumeration_operand.py +++ b/src/bespokeasm/assembler/model/operand/types/enumeration_operand.py @@ -63,7 +63,7 @@ def bytecode_value(self) -> int: def match_pattern(self) -> str: if self._argument_dictionary is not None: keys_str = '|'.join(self._argument_dictionary.keys()) - return r'\b({0})\b'.format(keys_str) + return fr'\b({keys_str})\b' else: return '' diff --git a/src/bespokeasm/assembler/model/operand/types/indexed_register.py b/src/bespokeasm/assembler/model/operand/types/indexed_register.py index 23986d7..cc2f5d2 100644 --- a/src/bespokeasm/assembler/model/operand/types/indexed_register.py +++ b/src/bespokeasm/assembler/model/operand/types/indexed_register.py @@ -39,7 +39,7 @@ def __init__(self, operand_id: str, arg_config_dict: dict, default_endian: str, self._index_parse_pattern = '|'.join(op_match_patterns) self._parse_pattern = re.compile( - r'^{0}$'.format(self.match_pattern), + fr'^{self.match_pattern}$', flags=(re.IGNORECASE | re.MULTILINE), ) diff --git a/src/bespokeasm/assembler/model/operand/types/indirect_indexed_register.py b/src/bespokeasm/assembler/model/operand/types/indirect_indexed_register.py index 7294233..b590f83 100644 --- a/src/bespokeasm/assembler/model/operand/types/indirect_indexed_register.py +++ b/src/bespokeasm/assembler/model/operand/types/indirect_indexed_register.py @@ -23,12 +23,12 @@ def match_pattern(self) -> str: self._index_parse_pattern, ) elif self.decorator_is_prefix: - pattern_str = r'(? OperandType: @cached_property def match_pattern(self) -> str: - return r'\[\s*({0})\s*\]'.format(super().match_pattern) + return fr'\[\s*({super().match_pattern})\s*\]' def parse_operand( self, diff --git a/src/bespokeasm/assembler/model/operand/types/indirect_register.py b/src/bespokeasm/assembler/model/operand/types/indirect_register.py index 8d73d07..6d7887b 100644 --- a/src/bespokeasm/assembler/model/operand/types/indirect_register.py +++ b/src/bespokeasm/assembler/model/operand/types/indirect_register.py @@ -16,7 +16,7 @@ class IndirectRegisterOperand(RegisterOperand): def __init__(self, operand_id: str, arg_config_dict: dict, default_endian: str, regsiters: set[str]) -> None: super().__init__(operand_id, arg_config_dict, default_endian, regsiters) self._parse_pattern = re.compile( - r'^{0}$'.format(self.match_pattern), + fr'^{self.match_pattern}$', flags=re.IGNORECASE | re.MULTILINE ) if self.has_offset: @@ -59,16 +59,16 @@ def match_pattern(self) -> str: if not self.has_decorator: pattern_str = IndirectRegisterOperand._BASE_PATTERN_TEMPLATE.format(self.register) elif self.decorator_is_prefix: - pattern_str = r'(? ParsedOperand: # do not match if expression contains square bracks - if "[" in operand or "]" in operand: + if '[' in operand or ']' in operand: return None bytecode_part = ExpressionByteCodePartWithValidation( diff --git a/src/bespokeasm/assembler/model/operand/types/register.py b/src/bespokeasm/assembler/model/operand/types/register.py index eddde54..8a3ac09 100644 --- a/src/bespokeasm/assembler/model/operand/types/register.py +++ b/src/bespokeasm/assembler/model/operand/types/register.py @@ -70,11 +70,11 @@ def decorator_is_prefix(self) -> bool: @property def match_pattern(self) -> str: if not self.has_decorator: - return r'\b{0}\b'.format(self.register) + return fr'\b{self.register}\b' elif self.decorator_is_prefix: - return r'(? ParsedOperand: # first check that operand is what we expect match = re.match( - r'^{0}$'.format(self.match_pattern), + fr'^{self.match_pattern}$', operand.strip(), flags=re.IGNORECASE, ) diff --git a/src/bespokeasm/assembler/model/operand/types/relative_address.py b/src/bespokeasm/assembler/model/operand/types/relative_address.py index 6b136f6..5402328 100644 --- a/src/bespokeasm/assembler/model/operand/types/relative_address.py +++ b/src/bespokeasm/assembler/model/operand/types/relative_address.py @@ -63,9 +63,9 @@ def type(self) -> OperandType: @cached_property def match_pattern(self) -> str: - base_match_str = r'((?:{0}|\s)+)'.format(EXPRESSION_PARTS_PATTERN) + base_match_str = fr'((?:{EXPRESSION_PARTS_PATTERN}|\s)+)' if self.uses_curly_braces: - return r'\{{\s*{0}\s*\}}'.format(base_match_str) + return fr'\{{\s*{base_match_str}\s*\}}' else: return base_match_str @@ -97,7 +97,7 @@ def parse_operand( if match is None or len(match.groups()) != 1: return None # do not match if expression contains square bracks - if "[" in operand or "]" in operand: + if '[' in operand or ']' in operand: return None bytecode_part = NumericByteCodePart( self.bytecode_value, diff --git a/src/bespokeasm/assembler/model/operand_parser.py b/src/bespokeasm/assembler/model/operand_parser.py index 213c1b1..1943007 100644 --- a/src/bespokeasm/assembler/model/operand_parser.py +++ b/src/bespokeasm/assembler/model/operand_parser.py @@ -40,7 +40,10 @@ def __init__(self, instruction: str, config: dict, operand_set_collection: Opera if opset is not None: self._operand_sets.append(opset) else: - sys.exit(f'ERROR: instuction set configuration file makes reference to unknow operand set "{k}"') + sys.exit( + f'ERROR: instuction set configuration file makes reference to unknow operand set "{k}"' + f' in definition of instruction "{instruction}"' + ) def __repr__(self) -> str: return str(self) diff --git a/src/bespokeasm/assembler/preprocessor/__init__.py b/src/bespokeasm/assembler/preprocessor/__init__.py index 873007f..b95b7a2 100644 --- a/src/bespokeasm/assembler/preprocessor/__init__.py +++ b/src/bespokeasm/assembler/preprocessor/__init__.py @@ -37,7 +37,7 @@ def create_symbol(self, name: str, value: str, line_id: LineIdentifier = None) - self._symbols[name] = symbol return symbol else: - raise ValueError(f"Symbol {name} already exists") + raise ValueError(f'Symbol {name} already exists') def get_symbol(self, name: str) -> PreprocessorSymbol: return self._symbols.get(name, None) diff --git a/src/bespokeasm/assembler/preprocessor/condition.py b/src/bespokeasm/assembler/preprocessor/condition.py index a66bdb9..a064b78 100644 --- a/src/bespokeasm/assembler/preprocessor/condition.py +++ b/src/bespokeasm/assembler/preprocessor/condition.py @@ -11,25 +11,25 @@ # NOTE: the order of the RHS expressions is important, as it determines the order of evaluation. Need to parse the # quoted strings first, then the expressions. PREPROCESSOR_CONDITION_IF_PATTERN = re.compile( - f"^(?:#if)\\s+({INSTRUCTION_EXPRESSION_PATTERN})\\s+(==|!=|>|>=|<|<=)\\s+" + f'^(?:#if)\\s+({INSTRUCTION_EXPRESSION_PATTERN})\\s+(==|!=|>|>=|<|<=)\\s+' f"(?:(?:\\\')(.+)(?:\\\')|(?:\\\")(.+)(?:\\\")|({INSTRUCTION_EXPRESSION_PATTERN}))" ) PREPROCESSOR_CONDITION_IMPLIED_IF_PATTERN = re.compile( - r"^(?:#if)\s+({0})".format(INSTRUCTION_EXPRESSION_PATTERN) + fr'^(?:#if)\s+({INSTRUCTION_EXPRESSION_PATTERN})' ) PREPROCESSOR_CONDITION_ELIF_PATTERN = re.compile( - f"^(?:#elif)\\s+({INSTRUCTION_EXPRESSION_PATTERN})\\s+(==|!=|>|>=|<|<=)\\s+" + f'^(?:#elif)\\s+({INSTRUCTION_EXPRESSION_PATTERN})\\s+(==|!=|>|>=|<|<=)\\s+' f"(?:(?:\\\')(.+)(?:\\\')|(?:\\\")(.+)(?:\\\")|({INSTRUCTION_EXPRESSION_PATTERN}))" ) PREPROCESSOR_CONDITION_IMPLIED_ELIF_PATTERN = re.compile( - r"^(?:#elif)\s+({0})".format(INSTRUCTION_EXPRESSION_PATTERN) + fr'^(?:#elif)\s+({INSTRUCTION_EXPRESSION_PATTERN})' ) PREPROCESSOR_CONDITION_IFDEF_PATTERN = re.compile( - r"^(#ifdef|#ifndef)\s+({0})\b".format(SYMBOL_PATTERN) + fr'^(#ifdef|#ifndef)\s+({SYMBOL_PATTERN})\b' ) @@ -46,6 +46,7 @@ def __str__(self) -> str: return self.__repr__() def evaluate(self, preprocessor: Preprocessor) -> bool: + """Evaluates whether this condition is true or false.""" raise NotImplementedError() def is_lineage_true(self, preprocessor: Preprocessor) -> bool: @@ -89,20 +90,20 @@ def _handle_matching( if match is None: match2 = implied_pattern.match(line_str.strip()) if match2 is None: - raise ValueError(f"Invalid preprocessor condition at line: {line_str}") + raise ValueError(f'Invalid preprocessor condition at line: {line_str}') self._lhs_expression = match2.group(1) - self._operator = "!=" - self._rhs_expression = "0" + self._operator = '!=' + self._rhs_expression = '0' else: self._lhs_expression = match.group(1) self._operator = match.group(2) self._rhs_expression = match.group(3) or match.group(4) or match.group(5) def __repr__(self) -> str: - return f"IfPreprocessorCondition<#if {self._lhs_expression} {self._operator} {self._rhs_expression}>" + return f'IfPreprocessorCondition<#if {self._lhs_expression} {self._operator} {self._rhs_expression}>' def _check_and_set_parent(self, parent: PreprocessorCondition): - raise ValueError("Cannot set parent of an IfPreprocessorCondition") + raise ValueError('Cannot set parent of an IfPreprocessorCondition') def _evaluate_condition(self, preprocessor: Preprocessor) -> bool: lhs_resolved = preprocessor.resolve_symbols(self._line, self._lhs_expression) @@ -120,20 +121,20 @@ def _evaluate_condition(self, preprocessor: Preprocessor) -> bool: lhs_value = lhs_expression.get_value(None, self._line) rhs_value = rhs_expression.get_value(None, self._line) - if self._operator == "==": + if self._operator == '==': return lhs_value == rhs_value - elif self._operator == "!=": + elif self._operator == '!=': return lhs_value != rhs_value - elif self._operator == ">": + elif self._operator == '>': return lhs_value > rhs_value - elif self._operator == ">=": + elif self._operator == '>=': return lhs_value >= rhs_value - elif self._operator == "<": + elif self._operator == '<': return lhs_value < rhs_value - elif self._operator == "<=": + elif self._operator == '<=': return lhs_value <= rhs_value else: - raise ValueError(f"Unknown operator {self._operator}") + raise ValueError(f'Unknown operator {self._operator}') def evaluate(self, preprocessor: Preprocessor) -> bool: return self._evaluate_condition(preprocessor) @@ -152,13 +153,13 @@ def __init__(self, line_str: str, line: LineIdentifier): ) def __repr__(self) -> str: - return f"ElifPreprocessorCondition<#elif {self._lhs_expression} {self._operator} {self._rhs_expression}>" + return f'ElifPreprocessorCondition<#elif {self._lhs_expression} {self._operator} {self._rhs_expression}>' def _check_and_set_parent(self, parent: PreprocessorCondition): if isinstance(parent, ElifPreprocessorCondition) or isinstance(parent, IfPreprocessorCondition): self._parent = parent else: - raise ValueError("#elif can only have #if or #elif as a parent") + raise ValueError('#elif can only have #if or #elif as a parent') @property def is_dependent(self) -> bool: @@ -169,7 +170,7 @@ def evaluate(self, preprocessor: Preprocessor) -> bool: # if its condition evaluates true. If the parent lineage is false, # this should evaluate to true if its condition evaluates true. if self.parent is None: - sys.exit(f"ERROR - Internal: parent condition not set for {self} at line {self._line}") + sys.exit(f'ERROR - Internal: parent condition not set for {self} at line {self._line}') if self.parent.is_lineage_true(preprocessor): return False return self._evaluate_condition(preprocessor) @@ -181,15 +182,15 @@ def __init__(self, line_str: str, line: LineIdentifier): match = PREPROCESSOR_CONDITION_IFDEF_PATTERN.match(line_str.strip()) if match is None: - raise ValueError(f"Invalid preprocessor condition at line: {line_str}") - self._is_ifndef = match.group(1) == "#ifndef" + raise ValueError(f'Invalid preprocessor condition at line: {line_str}') + self._is_ifndef = match.group(1) == '#ifndef' self._symbol = match.group(2) def __repr__(self) -> str: return f'IfdefPreprocessorCondition<{"#ifndef" if self._is_ifndef else "#ifdef"} {self._symbol}>' def _check_and_set_parent(self, parent: PreprocessorCondition): - raise ValueError("Cannot set parent of an IfdefPreprocessorCondition") + raise ValueError('Cannot set parent of an IfdefPreprocessorCondition') def evaluate(self, preprocessor: Preprocessor) -> bool: symbol = preprocessor.get_symbol(self._symbol) @@ -204,11 +205,11 @@ def __init__(self, line_str: str, line: LineIdentifier): super().__init__(line_str, line) def __repr__(self) -> str: - return "ElsePreprocessorCondition<#else>" + return 'ElsePreprocessorCondition<#else>' def _check_and_set_parent(self, parent: PreprocessorCondition): if isinstance(parent, ElsePreprocessorCondition) or isinstance(parent, EndifPreprocessorCondition): - raise ValueError("#else must have a conditional as a parent") + raise ValueError('#else must have a conditional as a parent') self._parent = parent @property @@ -217,7 +218,7 @@ def is_dependent(self) -> bool: def evaluate(self, preprocessor: Preprocessor) -> bool: if self.parent is None: - sys.exit(f"ERROR - Internal: parent condition not set for {self} at line {self._line}") + sys.exit(f'ERROR - Internal: parent condition not set for {self} at line {self._line}') return not self.parent.is_lineage_true(preprocessor) @@ -226,11 +227,11 @@ def __init__(self, line_str: str, line: LineIdentifier): super().__init__(line_str, line) def __repr__(self) -> str: - return "EndifPreprocessorCondition<#endif>" + return 'EndifPreprocessorCondition<#endif>' def _check_and_set_parent(self, parent: PreprocessorCondition): if isinstance(parent, EndifPreprocessorCondition): - raise ValueError("#endif must have a conditional as a parent") + raise ValueError('#endif must have a conditional as a parent') self._parent = parent @property @@ -239,3 +240,25 @@ def is_dependent(self) -> bool: def evaluate(self, preprocessor: Preprocessor) -> bool: return True + + +class MutePreprocessorCondition(PreprocessorCondition): + def __init__(self, line_str: str, line: LineIdentifier): + super().__init__(line_str, line) + + def __repr__(self) -> str: + return f'MutePreprocessorCondition<{self.self._line_str}>' + + def evaluate(self, preprocessor: Preprocessor) -> bool: + return True + + +class UnmutePreprocessorCondition(PreprocessorCondition): + def __init__(self, line_str: str, line: LineIdentifier): + super().__init__(line_str, line) + + def __repr__(self) -> str: + return f'UnmutePreprocessorCondition<{self.self._line_str}>' + + def evaluate(self, preprocessor: Preprocessor) -> bool: + return True diff --git a/src/bespokeasm/assembler/preprocessor/condition_stack.py b/src/bespokeasm/assembler/preprocessor/condition_stack.py index 02ce9b0..fdd91d7 100644 --- a/src/bespokeasm/assembler/preprocessor/condition_stack.py +++ b/src/bespokeasm/assembler/preprocessor/condition_stack.py @@ -1,18 +1,25 @@ - from bespokeasm.assembler.preprocessor.condition import \ - PreprocessorCondition, EndifPreprocessorCondition + PreprocessorCondition, EndifPreprocessorCondition, \ + MutePreprocessorCondition, UnmutePreprocessorCondition from bespokeasm.assembler.preprocessor import Preprocessor class ConditionStack: def __init__(self): self._stack: list[PreprocessorCondition] = [] + self._mute_counter = 0 - def process_condition(self, condition: PreprocessorCondition): + def process_condition(self, condition: PreprocessorCondition, preprocessor: Preprocessor): if isinstance(condition, EndifPreprocessorCondition): popped_consition = self._stack.pop() if popped_consition.is_dependent: pass + elif isinstance(condition, MutePreprocessorCondition): + if self.currently_active(preprocessor): + self._increment_mute_counter() + elif isinstance(condition, UnmutePreprocessorCondition): + if self.currently_active(preprocessor): + self._decrement_mute_counter() elif condition.is_dependent: # a dependent condition pops the current condition and makes it the parent to the new condition. # this way the dependent chain is only ever 1-deep in the stack, making nested #if/#else/#endif @@ -27,3 +34,15 @@ def currently_active(self, preprocessor: Preprocessor) -> bool: if len(self._stack) == 0: return True return self._stack[-1].evaluate(preprocessor) + + def _increment_mute_counter(self): + self._mute_counter += 1 + + def _decrement_mute_counter(self): + if self._mute_counter > 0: + self._mute_counter -= 1 + + @property + def is_muted(self) -> bool: + """Returns True if the current condition stack is muted.""" + return self._mute_counter > 0 diff --git a/src/bespokeasm/assembler/preprocessor/symbol.py b/src/bespokeasm/assembler/preprocessor/symbol.py index 71b6b83..cc26707 100644 --- a/src/bespokeasm/assembler/preprocessor/symbol.py +++ b/src/bespokeasm/assembler/preprocessor/symbol.py @@ -4,7 +4,7 @@ from bespokeasm.assembler.line_identifier import LineIdentifier -SYMBOL_PATTERN = r"[\w_][\w\d_]+" +SYMBOL_PATTERN = r'[\w_][\w\d_]+' class PreprocessorSymbol: @@ -14,7 +14,7 @@ def __init__(self, name: str, value: str, line_id: LineIdentifier = None): self._line_id = line_id def __repr__(self) -> str: - return f"PrerocessorSymbol({self.name} = {self.value})" + return f'PrerocessorSymbol({self.name} = {self.value})' def __eq__(self, other) -> bool: return self.name == other.name and self.value == other.value diff --git a/src/bespokeasm/assembler/pretty_printer/intelhex.py b/src/bespokeasm/assembler/pretty_printer/intelhex.py index 2a10573..89dda60 100644 --- a/src/bespokeasm/assembler/pretty_printer/intelhex.py +++ b/src/bespokeasm/assembler/pretty_printer/intelhex.py @@ -5,7 +5,7 @@ from bespokeasm.assembler.line_object import LineWithBytes, LineObject from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.pretty_printer import PrettyPrinterBase -from bespokeasm.assembler.line_object.directive_line import AddressOrgLine +from bespokeasm.assembler.line_object.directive_line.address import AddressOrgLine class IntelHexPrettyPrinter(PrettyPrinterBase): @@ -17,7 +17,7 @@ def __init__(self, line_objs: list[LineObject], model: AssemblerModel, as_intel def pretty_print(self) -> str: output = io.StringIO() for lobj in self.line_objects: - if isinstance(lobj, LineWithBytes): + if isinstance(lobj, LineWithBytes) and not lobj.is_muted: line_bytes = lobj.get_bytes().decode(encoding='latin-') self._intel_hex.puts(lobj.address, line_bytes) diff --git a/src/bespokeasm/assembler/pretty_printer/listing.py b/src/bespokeasm/assembler/pretty_printer/listing.py index 8e4ca47..8f754cd 100644 --- a/src/bespokeasm/assembler/pretty_printer/listing.py +++ b/src/bespokeasm/assembler/pretty_printer/listing.py @@ -4,7 +4,7 @@ from bespokeasm.assembler.line_object import LineObject, LineWithBytes from bespokeasm.assembler.line_object.label_line import LabelLine -from bespokeasm.assembler.line_object.directive_line import SetMemoryZoneLine +from bespokeasm.assembler.line_object.directive_line.memzone import SetMemoryZoneLine from bespokeasm.assembler.line_object.preprocessor_line import PreprocessorLine from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.pretty_printer import PrettyPrinterBase @@ -52,13 +52,16 @@ def pretty_print(self) -> str: return output.getvalue() def _print_file_header(self, output: io.StringIO, filename: str) -> None: + COMMENT_HEADER = 'comment' + comment_header_width = len(COMMENT_HEADER) if len(COMMENT_HEADER) > self.max_comment_width else self.max_comment_width + output.write(f'\n\nFile: {filename}\n') output.write( '-'*(self.max_line_num_width + 2) + '+' + '-'*(self._address_size + 2) + '+' + '-'*(self._bytes_per_line*3 + 1) + '+' + '-'*(self.max_instruction_width + 2) + '+' + - '-'*(self.max_comment_width + 2) + '\n' + '-'*(comment_header_width + 2) + '\n' ) if self._address_size >= 8: @@ -68,16 +71,16 @@ def _print_file_header(self, output: io.StringIO, filename: str) -> None: else: address_header = 'a' - if self._bytes_per_line >= 4: + if self._bytes_per_line >= 5: bytes_header = 'machine code' elif self._bytes_per_line >= 2: bytes_header = 'bytes' else: bytes_header = 'b' - if self.max_instruction_width >= 10: + if self.max_instruction_width >= 11: instruction_header = ' instruction' - elif self.max_instruction_width >= 5: + elif self.max_instruction_width >= 6: instruction_header = ' instr' else: instruction_header = ' i' @@ -87,7 +90,7 @@ def _print_file_header(self, output: io.StringIO, filename: str) -> None: address_header.center(self._address_size + 2) + '|' + bytes_header.center(self._bytes_per_line*3 + 1) + '|' + instruction_header.ljust(self.max_instruction_width + 2) + '|' + - ' comment\n' + f' {COMMENT_HEADER}\n' ) output.write( @@ -95,7 +98,7 @@ def _print_file_header(self, output: io.StringIO, filename: str) -> None: '-'*(self._address_size + 2) + '+' + '-'*(self._bytes_per_line*3 + 1) + '+' + '-'*(self.max_instruction_width + 2) + '+' + - '-'*(self.max_comment_width + 2) + '\n' + '-'*(comment_header_width + 2) + '\n' ) def _print_line_object(self, output: io.StringIO, lobj: LineObject) -> None: diff --git a/src/bespokeasm/assembler/pretty_printer/minhex.py b/src/bespokeasm/assembler/pretty_printer/minhex.py index c5a4c4d..7791ed9 100644 --- a/src/bespokeasm/assembler/pretty_printer/minhex.py +++ b/src/bespokeasm/assembler/pretty_printer/minhex.py @@ -3,7 +3,7 @@ from bespokeasm.assembler.line_object import LineWithBytes, LineObject from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.pretty_printer import PrettyPrinterBase -from bespokeasm.assembler.line_object.directive_line import AddressOrgLine +from bespokeasm.assembler.line_object.directive_line.address import AddressOrgLine class MinHexPrettyPrinter(PrettyPrinterBase): @@ -15,7 +15,7 @@ def pretty_print(self) -> str: line_byte_count = 0 address_width = int(self.model.address_size/4) for lobj in self.line_objects: - if isinstance(lobj, LineWithBytes): + if isinstance(lobj, LineWithBytes) and not lobj.is_muted: line_bytes = lobj.get_bytes() for b in line_bytes: if line_byte_count == 0: diff --git a/src/bespokeasm/assembler/pretty_printer/source_details.py b/src/bespokeasm/assembler/pretty_printer/source_details.py index d91ca16..ecb2429 100644 --- a/src/bespokeasm/assembler/pretty_printer/source_details.py +++ b/src/bespokeasm/assembler/pretty_printer/source_details.py @@ -27,14 +27,14 @@ def pretty_print(self) -> str: blank_byte_text = ''.join([' '*COL_WIDTH_BYTE]) blank_binary_text = ''.join([' '*COL_WIDTH_BINARY]) - header_text = ' {0} | {1} | {2} | {3} | {4} | Comment '.format( + header_text = ' {} | {} | {} | {} | {} | Comment '.format( 'Line'.center(COL_WIDTH_LINE), 'Code'.ljust(self.max_instruction_width + INSTRUCTION_INDENT), 'Address'.center(COL_WIDTH_ADDRESS), 'Byte'.center(COL_WIDTH_BYTE), 'Binary'.center(COL_WIDTH_BINARY), ) - header_line_text = '-{0}-+-{1}-+-{2}-+-{3}-+-{4}-+---------------'.format( + header_line_text = '-{}-+-{}-+-{}-+-{}-+-{}-+---------------'.format( ''.join('-'*(COL_WIDTH_LINE)), ''.join('-'*(self.max_instruction_width)), ''.join('-'*(COL_WIDTH_ADDRESS)), diff --git a/src/bespokeasm/configgen/sublime/__init__.py b/src/bespokeasm/configgen/sublime/__init__.py index e2654e9..57acc4d 100644 --- a/src/bespokeasm/configgen/sublime/__init__.py +++ b/src/bespokeasm/configgen/sublime/__init__.py @@ -45,7 +45,7 @@ def _generate_files_in_dir(self, destination_dir: str) -> None: # generate syntax file fp = pkg_resources.files(resources).joinpath('sublime-syntax.yaml') - with open(fp, 'r') as syntax_file: + with open(fp) as syntax_file: try: syntax_dict = yaml.safe_load(syntax_file) except yaml.YAMLError as exc: @@ -166,10 +166,10 @@ def _generate_files_in_dir(self, destination_dir: str) -> None: yaml.dump(syntax_dict, f) # now reinsert the YAML prefix. This is required due to an odditity in Sublime's package loading. # I don't know a better way to do this. - with open(syntax_fp, "r") as f: + with open(syntax_fp) as f: file_txt = f.read() updated_file_txt = '%YAML 1.2\n---\n' + file_txt - with open(syntax_fp, "w") as f: + with open(syntax_fp, 'w') as f: f.write(updated_file_txt) if self.verbose > 1: print(f' generated {os.path.basename(syntax_fp)}') diff --git a/src/bespokeasm/configgen/sublime/resources/sublime-color-scheme.json b/src/bespokeasm/configgen/sublime/resources/sublime-color-scheme.json index 6f58d0f..3903e3a 100644 --- a/src/bespokeasm/configgen/sublime/resources/sublime-color-scheme.json +++ b/src/bespokeasm/configgen/sublime/resources/sublime-color-scheme.json @@ -38,7 +38,7 @@ { "name": "String Punctuation", "scope": "punctuation.definition.string", - "foreground": "#e7ffcd" + "foreground": "#ed80a2" }, { diff --git a/src/bespokeasm/configgen/sublime/resources/sublime-syntax.yaml b/src/bespokeasm/configgen/sublime/resources/sublime-syntax.yaml index c4c1a67..f24b0b6 100644 --- a/src/bespokeasm/configgen/sublime/resources/sublime-syntax.yaml +++ b/src/bespokeasm/configgen/sublime/resources/sublime-syntax.yaml @@ -31,6 +31,7 @@ contexts: push: - meta_scope: meta.function - include: pop_instruction_end + - include: strings - include: registers - include: compiler_labels - include: indirect_addressing @@ -44,6 +45,7 @@ contexts: push: - meta_scope: meta.function - include: pop_instruction_end + - include: strings - include: registers - include: compiler_labels - include: indirect_addressing diff --git a/src/bespokeasm/configgen/vscode/__init__.py b/src/bespokeasm/configgen/vscode/__init__.py index 511da75..a75f8d3 100644 --- a/src/bespokeasm/configgen/vscode/__init__.py +++ b/src/bespokeasm/configgen/vscode/__init__.py @@ -34,7 +34,7 @@ def generate(self) -> None: Path(os.path.join(extension_dir_path, 'syntaxes')).mkdir(parents=True, exist_ok=True) # generate package.json fp = pkg_resources.files(resources).joinpath('package.json') - with open(fp, 'r') as json_file: + with open(fp) as json_file: package_json = json.load(json_file) scope_name = 'source.' + self.language_id @@ -47,6 +47,8 @@ def generate(self) -> None: package_json['contributes']['grammars'][0]['language'] = self.language_id package_json['contributes']['grammars'][0]['scopeName'] = scope_name package_json['contributes']['snippets'][0]['language'] = self.language_id + package_json['contributes']['themes'][0]['label'] = \ + package_json['contributes']['themes'][0]['label'].replace('##LANGUAGE_ID##', self.language_name) package_json['contributes']['themes'][0]['path'] = './' + theme_filename package_fp = os.path.join(extension_dir_path, 'package.json') @@ -57,7 +59,7 @@ def generate(self) -> None: # generate tmGrammar.json fp = pkg_resources.files(resources).joinpath('tmGrammar.json') - with open(fp, 'r') as json_file: + with open(fp) as json_file: grammar_json = json.load(json_file) grammar_json['scopeName'] = scope_name @@ -161,6 +163,10 @@ def generate(self) -> None: print(f' generated {os.path.basename(str(fp))}') fp = pkg_resources.files(resources).joinpath('tmTheme.xml') - shutil.copy(str(fp), os.path.join(extension_dir_path, theme_filename)) - if self.verbose > 1: - print(f' generated {theme_filename}') + with open(fp) as theme_template_file: + color_theme_xml = theme_template_file.read() + color_theme_xml.replace('##LANGUAGE_ID##', self.language_id) + with open(os.path.join(extension_dir_path, theme_filename), 'w') as theme_file: + theme_file.write(color_theme_xml) + if self.verbose > 1: + print(f' generated {theme_filename}') diff --git a/src/bespokeasm/configgen/vscode/resources/package.json b/src/bespokeasm/configgen/vscode/resources/package.json index 66883db..05cdea8 100644 --- a/src/bespokeasm/configgen/vscode/resources/package.json +++ b/src/bespokeasm/configgen/vscode/resources/package.json @@ -22,7 +22,7 @@ "path": "./snippets.json" }], "themes": [{ - "label": "BespokeASM Theme", + "label": "##LANGUAGE_ID## Theme", "uiTheme": "vs-dark", "path": "./bespokeasm.tmTheme" }] diff --git a/src/bespokeasm/configgen/vscode/resources/tmGrammar.json b/src/bespokeasm/configgen/vscode/resources/tmGrammar.json index 727b6a4..713f461 100644 --- a/src/bespokeasm/configgen/vscode/resources/tmGrammar.json +++ b/src/bespokeasm/configgen/vscode/resources/tmGrammar.json @@ -23,6 +23,7 @@ "instructions": { "name": "meta.function", "patterns": [ + { "include": "#strings" }, { "include": "#registers" }, { "include": "#compiler_labels" }, { "include": "#numeric-values" }, @@ -45,6 +46,7 @@ "macros": { "name": "meta.function", "patterns": [ + { "include": "#strings" }, { "include": "#registers" }, { "include": "#compiler_labels" }, { "include": "#numeric-values" }, @@ -84,7 +86,7 @@ } }, "constants": { - "match": "^\\s*(\\w*)(?:\\s*)?(\\=|EQU)\\b", + "match": "^\\s*(\\w*)(?:\\s*)?(\\=|EQU)", "captures": { "1": { "name": "variable.other.constant" }, "2": { "name": "keyword.operator.assignment" } diff --git a/src/bespokeasm/configgen/vscode/resources/tmTheme.xml b/src/bespokeasm/configgen/vscode/resources/tmTheme.xml index f3cca61..7d10267 100644 --- a/src/bespokeasm/configgen/vscode/resources/tmTheme.xml +++ b/src/bespokeasm/configgen/vscode/resources/tmTheme.xml @@ -3,7 +3,7 @@ name - BespokeASM Color Scheme + ##LANGUAGE_ID## Color Scheme settings @@ -82,7 +82,7 @@ settings foreground - #e7ffcd + #ed80a2 @@ -148,7 +148,7 @@ settings foreground - #ff883f + #ff770c diff --git a/src/bespokeasm/expression/__init__.py b/src/bespokeasm/expression/__init__.py index 04c69bb..207e23b 100644 --- a/src/bespokeasm/expression/__init__.py +++ b/src/bespokeasm/expression/__init__.py @@ -18,7 +18,7 @@ from bespokeasm.utilities import is_valid_label EXPRESSION_PARTS_PATTERN = \ - r'(?:(?:\%|b)[01]+|{}|\d+|[\+\-\*\/\&\|\^\(\)]|>>|<<|%|LSB\(|BYTE\d\(|(?:\.|_)?\w+|\'.\')'.format( + r'(?:(?:\%|b)[01]+|{}|\d+|[\+\-\*\/\&\|\^\(\)]|>>|<<|%|LSB\(|BYTE\d\(|(?:\.|_)?\w+|\'.\'|[><])'.format( PATTERN_HEX ) diff --git a/src/bespokeasm/utilities.py b/src/bespokeasm/utilities.py index 97896f3..f9a6a9b 100644 --- a/src/bespokeasm/utilities.py +++ b/src/bespokeasm/utilities.py @@ -2,7 +2,7 @@ PATTERN_HEX = r'(?:\$|0x)[0-9a-fA-F]+|[0-9a-fA-F]+H\b' -PATTERN_NUMERIC = r'(?:{0}|(?:b|%)[01]+|\d+|\'.\')'.format(PATTERN_HEX) +PATTERN_NUMERIC = fr'(?:{PATTERN_HEX}|(?:b|%)[01]+|\d+|\'.\')' PATTERN_NUMERIC_COMPILED = re.compile(f'^({PATTERN_NUMERIC})$', flags=re.IGNORECASE | re.MULTILINE) PATTERN_CHARACTER_ORDINAL = re.compile(r'\'(.)\'', flags=re.IGNORECASE | re.MULTILINE) diff --git a/test/__init__.py b/test/__init__.py index 7d004ee..07dabc1 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -3,7 +3,7 @@ PROJECT_PATH = os.getcwd() SOURCE_PATH = os.path.join( - PROJECT_PATH, "src" + PROJECT_PATH, 'src' ) sys.path.append(SOURCE_PATH) diff --git a/test/config_files/test_instructions_with_periods.yaml b/test/config_files/test_instructions_with_periods.yaml new file mode 100644 index 0000000..866a42f --- /dev/null +++ b/test/config_files/test_instructions_with_periods.yaml @@ -0,0 +1,125 @@ +--- +description: Test Instructions with Periods +general: + address_size: 16 + endian: little + registers: + - a + - h + - l + - hl + - sp + identifier: + name: test_instructions_with_periods +operand_sets: + 8_bit_source: + operand_values: + register_a: + type: register + register: a + bytecode: + value: 0 + size: 3 + indirect_sp: + type: indirect_register + register: sp + bytecode: + value: 4 + size: 3 + offset: + max: 127 + min: -128 + size: 8 + byte_align: true + indirect_addr: + type: indirect_numeric + bytecode: + value: 6 + size: 3 + argument: + size: 16 + byte_align: true + direct_value: + type: numeric + bytecode: + value: 7 + size: 3 + argument: + size: 8 + byte_align: true + 8_bit_destination: + operand_values: + register_a: + type: register + register: a + bytecode: + value: 0 + size: 3 + indirect_sp: + type: indirect_register + register: sp + bytecode: + value: 4 + size: 3 + offset: + max: 127 + min: -128 + size: 8 + byte_align: true + indirect_addr: + type: indirect_numeric + bytecode: + value: 6 + size: 3 + argument: + size: 16 + byte_align: true + hl_subregisters: + operand_values: + subregister_h: + type: register + register: h + bytecode: + value: 1 + size: 1 + subregister_l: + type: register + register: l + bytecode: + value: 0 + size: 1 +instructions: + nop: + bytecode: + value: 0 + size: 4 + ma.hl: + bytecode: + value: 1 + size: 2 + operands: + count: 2 + operand_sets: + list: + - 8_bit_destination + - 8_bit_source + specific_operands: + a_indirect_hl: + list: + register_a: + type: register + register: a + bytecode: + value: 0 + size: 3 + indirect_hl: + type: indirect_register + register: hl + bytecode: + value: 5 + size: 3 + offset: + max: 127 + min: -128 + size: 8 + byte_align: true diff --git a/test/config_files/test_valid_address_enforcement.yaml b/test/config_files/test_valid_address_enforcement.yaml index 8d5765a..6de4a60 100644 --- a/test/config_files/test_valid_address_enforcement.yaml +++ b/test/config_files/test_valid_address_enforcement.yaml @@ -16,6 +16,9 @@ predefined: - name: GLOBAL start: 0x2000 end: 0xBFFF + - name: TEST1 + start: 0x5000 + end: 0x5FFF operand_sets: register: operand_values: @@ -51,7 +54,7 @@ operand_sets: addresses: operand_values: address: - type: numeric + type: address bytecode: value: 5 size: 4 @@ -148,3 +151,50 @@ instructions: byte_align: true max: 128 min: -127 + jmp_local: + bytecode: + value: 0xE0 + size: 8 + operands: + count: 1 + specific_operands: + local_addr: + list: + local_addr: + type: address + argument: + size: 8 + byte_align: true + slice_lsb: true + match_address_msb: true + jmpa: + bytecode: + value: 0xE1 + size: 8 + operands: + count: 1 + specific_operands: + address: + list: + address: + type: address + argument: + size: 16 + byte_align: true + valid_address: true + jmp_test1: + bytecode: + value: 0xE2 + size: 8 + operands: + count: 1 + specific_operands: + address: + list: + address: + type: address + argument: + size: 16 + byte_align: true + valid_address: true + memory_zone: TEST1 diff --git a/test/test_assembler_model.py b/test/test_assembler_model.py index 7c7781e..6d67c35 100644 --- a/test/test_assembler_model.py +++ b/test/test_assembler_model.py @@ -30,14 +30,14 @@ def setUpClass(cls): def test_argument_set_construction(self): conf1 = yaml.safe_load(self._eater_sap1_config_str) - arg_collection1 = AS.OperandSetCollection(conf1['operand_sets'], 'big', set([])) + arg_collection1 = AS.OperandSetCollection(conf1['operand_sets'], 'big', set()) self.assertEqual(len(arg_collection1), 2, 'there are 2 argument sets') self.assertTrue('integer' in arg_collection1) self.assertTrue('address' in arg_collection1) conf2 = yaml.safe_load(self._register_argument_config_str) arg_collection2 = AS.OperandSetCollection( - conf2['operand_sets'], 'little', set(['a', 'i', 'j', 'sp', 'ij', 'mar']) + conf2['operand_sets'], 'little', {'a', 'i', 'j', 'sp', 'ij', 'mar'} ) self.assertEqual(len(arg_collection2), 4, 'there are 4 argument sets') self.assertTrue('8_bit_source' in arg_collection2) @@ -243,7 +243,7 @@ def test_predefined_entities(self): fp = pkg_resources.files(config_files).joinpath('test_compiler_features.yaml') model = AssemblerModel(str(fp), 0) - self.assertSetEqual(set(model.predefined_labels), set(['CONST1', 'CONST2', 'buffer']), 'label set should equal') + self.assertSetEqual(set(model.predefined_labels), {'CONST1', 'CONST2', 'buffer'}, 'label set should equal') def test_mnemonic_lists(self): fp = pkg_resources.files(config_files).joinpath('test_instruction_macros.yaml') @@ -251,12 +251,12 @@ def test_mnemonic_lists(self): self.assertSetEqual( model.instruction_mnemonics, - set(['push', 'pop', 'mov', 'add', 'addc', 'ldar']), + {'push', 'pop', 'mov', 'add', 'addc', 'ldar'}, 'instruction mnomonics should match' ) self.assertSetEqual( model.macro_mnemonics, - set(['push2', 'mov2', 'add16', 'swap', 'incs']), + {'push2', 'mov2', 'add16', 'swap', 'incs'}, 'macro mnomonics should match' ) self.assertListEqual( diff --git a/test/test_bytecode.py b/test/test_bytecode.py index 6646366..8119608 100644 --- a/test/test_bytecode.py +++ b/test/test_bytecode.py @@ -37,7 +37,7 @@ def test_bytecode_assembly(self): def test_composite_bytecode_part(self): test_line_id = LineIdentifier(88, 'test_composite_bytecode_part') - register_labels = set(['a', 'i']) + register_labels = {'a', 'i'} label_values = GlobalLabelScope(register_labels) label_values.set_label_value('var1', 2, 1) label_values.set_label_value('var2', 0xF0, 2) diff --git a/test/test_configgen.py b/test/test_configgen.py index 008876a..cb1f8cb 100644 --- a/test/test_configgen.py +++ b/test/test_configgen.py @@ -18,7 +18,7 @@ class TestConfigurationGeneration(unittest.TestCase): def assertIsFile(self, path): if not pl.Path(path).resolve().is_file(): - raise AssertionError("File does not exist: %s" % str(path)) + raise AssertionError('File does not exist: %s' % str(path)) def _assert_grouped_item_list(self, item_str: str, target_list: list[str], test_name: str) -> None: match = re.search(r'^.*(?<=[\w\)])?\((?:\?\:)?(.*)\)', item_str, re.IGNORECASE) @@ -46,7 +46,7 @@ def test_vscode_configgen_no_registers(self): package_fp = os.path.join(extension_dirpath, 'bespokeasm-test', 'package.json') self.assertIsFile(package_fp) - with open(package_fp, 'r') as json_file: + with open(package_fp) as json_file: package_json = json.load(json_file) self.assertEqual( package_json['name'], @@ -86,7 +86,7 @@ def test_vscode_configgen_no_registers(self): grammar_fp = os.path.join(extension_dirpath, 'bespokeasm-test', 'syntaxes', 'tmGrammar.json') self.assertIsFile(grammar_fp) - with open(grammar_fp, 'r') as json_file: + with open(grammar_fp) as json_file: grammar_json = json.load(json_file) self._assert_grouped_item_list( grammar_json['repository']['instructions']['begin'], @@ -131,7 +131,7 @@ def test_vscode_configgen_with_registers(self): package_fp = os.path.join(extension_dirpath, 'tester-assembly', 'package.json') self.assertIsFile(package_fp) - with open(package_fp, 'r') as json_file: + with open(package_fp) as json_file: package_json = json.load(json_file) self.assertEqual( package_json['name'], @@ -166,7 +166,7 @@ def test_vscode_configgen_with_registers(self): grammar_fp = os.path.join(extension_dirpath, 'tester-assembly', 'syntaxes', 'tmGrammar.json') self.assertIsFile(grammar_fp) - with open(grammar_fp, 'r') as json_file: + with open(grammar_fp) as json_file: grammar_json = json.load(json_file) self._assert_grouped_item_list( grammar_json['repository']['instructions']['begin'], @@ -203,7 +203,7 @@ def test_sublime_configgen_no_registers(self): syntax_fp = os.path.join(test_tmp_dir, 'bespokeasm-test.sublime-syntax') self.assertIsFile(syntax_fp) - with open(syntax_fp, 'r') as yaml_file: + with open(syntax_fp) as yaml_file: syntax_dict = yaml.safe_load(yaml_file) self.assertListEqual(syntax_dict['file_extensions'], ['asmtest'], 'file extension should be as assigned') @@ -228,7 +228,7 @@ def test_sublime_configgen_no_registers(self): self.assertFalse(('registers' in syntax_dict['contexts']), 'no registers should be found') self._assert_grouped_item_list( syntax_dict['contexts']['compiler_directives'][0]['match'], - ['\\.org', '\\.memzone'], + ['\\.org', '\\.memzone', '\\.align',], 'compiler directives' ) self._assert_grouped_item_list( @@ -245,7 +245,11 @@ def test_sublime_configgen_no_registers(self): item_match_str = item['match'] self._assert_grouped_item_list( item_match_str, - ['include', 'require', 'create_memzone', 'define', 'if', 'elif', 'else', 'endif', 'ifdef', 'ifndef'], + [ + 'include', 'require', 'create_memzone', 'define', 'if', + 'elif', 'else', 'endif', 'ifdef', 'ifndef', + 'mute', 'unmute', 'emit', + ], 'data type directives' ) self.assertIsFile(os.path.join(test_tmp_dir, 'bespokeasm-test.sublime-color-scheme')) @@ -279,7 +283,7 @@ def test_sublime_configgen_with_registers(self): syntax_fp = os.path.join(test_tmp_dir, 'tester-assembly.sublime-syntax') self.assertIsFile(syntax_fp) - with open(syntax_fp, 'r') as yaml_file: + with open(syntax_fp) as yaml_file: syntax_dict = yaml.safe_load(yaml_file) self.assertListEqual(syntax_dict['file_extensions'], ['asmtest'], 'file extension should be as assigned') @@ -308,7 +312,7 @@ def test_sublime_configgen_with_registers(self): ) self._assert_grouped_item_list( syntax_dict['contexts']['compiler_directives'][0]['match'], - ['\\.org', '\\.memzone'], + ['\\.org', '\\.memzone', '\\.align',], 'compiler directives' ) self._assert_grouped_item_list( @@ -326,7 +330,11 @@ def test_sublime_configgen_with_registers(self): item_match_str = item['match'] self._assert_grouped_item_list( item_match_str, - ['include', 'require', 'create_memzone', 'define', 'if', 'elif', 'else', 'endif', 'ifdef', 'ifndef'], + [ + 'include', 'require', 'create_memzone', 'define', + 'if', 'elif', 'else', 'endif', 'ifdef', 'ifndef', + 'mute', 'unmute', 'emit', + ], 'data type directives' ) self.assertIsFile(os.path.join(test_tmp_dir, 'tester-assembly.sublime-color-scheme')) diff --git a/test/test_directive_lines.py b/test/test_directive_lines.py index a91b891..696cc50 100644 --- a/test/test_directive_lines.py +++ b/test/test_directive_lines.py @@ -1,14 +1,20 @@ import unittest import importlib.resources as pkg_resources +import copy from test import config_files from bespokeasm.assembler.label_scope import GlobalLabelScope from bespokeasm.assembler.label_scope import LabelScope, LabelScopeType -from bespokeasm.assembler.line_object.directive_line import DirectiveLine, AddressOrgLine, FillDataLine, FillUntilDataLine +from bespokeasm.assembler.line_object import LineObject +from bespokeasm.assembler.line_object.directive_line.fill_data import FillDataLine, FillUntilDataLine +from bespokeasm.assembler.line_object.directive_line.factory import DirectiveLine +from bespokeasm.assembler.line_object.directive_line.address import AddressOrgLine +from bespokeasm.assembler.line_object.directive_line.page_align import PageAlignLine from bespokeasm.assembler.line_object import LineWithBytes from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager +from bespokeasm.assembler.line_identifier import LineIdentifier class TestDirectiveLines(unittest.TestCase): @@ -39,6 +45,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o1, AddressOrgLine) self.assertEqual(o1.address, 1) @@ -53,6 +60,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o2, AddressOrgLine) self.assertEqual(o2.address, 8) @@ -64,6 +72,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o3, AddressOrgLine) self.assertEqual(o3.address, 4) @@ -75,6 +84,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o4, AddressOrgLine) self.assertEqual(o4.address, 15) @@ -86,6 +96,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) o5.label_scope = label_values self.assertIsInstance(o5, AddressOrgLine) @@ -99,6 +110,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) e1.label_scope = label_values e1.address @@ -111,6 +123,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) o6.label_scope = label_values self.assertIsInstance(o6, AddressOrgLine) @@ -123,6 +136,7 @@ def test_org_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) o7.label_scope = label_values self.assertIsInstance(o7, AddressOrgLine) @@ -143,6 +157,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o1, FillDataLine) o1.label_scope = label_values @@ -157,6 +172,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o2, FillDataLine) o2.label_scope = label_values @@ -171,6 +187,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o2b, FillDataLine) o2b.label_scope = label_values @@ -185,6 +202,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o3, FillDataLine) o3.label_scope = label_values @@ -200,6 +218,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o4, FillDataLine) o4.label_scope = label_values @@ -214,6 +233,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o5, FillDataLine) o5.label_scope = label_values @@ -228,6 +248,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o6, FillDataLine) o6.label_scope = label_values @@ -242,6 +263,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o6, FillDataLine) o6.label_scope = label_values @@ -256,6 +278,7 @@ def test_fill_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o7, FillDataLine) o7.label_scope = label_values @@ -274,6 +297,7 @@ def test_filluntil_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o1, FillUntilDataLine) o1.set_start_address(0x42) @@ -290,6 +314,7 @@ def test_filluntil_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o2, FillUntilDataLine) o2.set_start_address(0xF) @@ -305,6 +330,7 @@ def test_filluntil_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model, ) self.assertIsInstance(o3, FillUntilDataLine) o3.set_start_address(0xF) @@ -316,15 +342,17 @@ def test_filluntil_directive(self): def test_cstr_directive(self): label_values = LabelScope(LabelScopeType.GLOBAL, None, 'global') label_values.set_label_value('my_label', 0x80, 1) + local_isa_model = copy.deepcopy(TestDirectiveLines.isa_model) - t1 = DirectiveLine.factory( + local_isa_model._config['general']['cstr_terminator'] = 0 + t1: LineObject = DirectiveLine.factory( 1234, '.cstr "this is a test"', 'that str', TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, - 0, + local_isa_model ) self.assertIsInstance(t1, LineWithBytes) t1.set_start_address(0xF) @@ -333,6 +361,7 @@ def test_cstr_directive(self): t1.generate_bytes() self.assertEqual(list(t1.get_bytes())[-1], 0, 'terminating character must match') + local_isa_model._config['general']['cstr_terminator'] = 3 t2 = DirectiveLine.factory( 1234, '.cstr "this is a test"', @@ -340,7 +369,7 @@ def test_cstr_directive(self): TestDirectiveLines.isa_model.endian, TestDirectiveLines.memzone, TestDirectiveLines.memory_zone_manager, - 3, + local_isa_model ) self.assertIsInstance(t1, LineWithBytes) t2.set_start_address(0xF) @@ -349,6 +378,65 @@ def test_cstr_directive(self): t2.generate_bytes() self.assertEqual(list(t2.get_bytes())[-1], 3, 'terminating character must match') + def test_page_align_line(self): + t1: LineObject = DirectiveLine.factory( + 1234, + '.align', + 'page align', + TestDirectiveLines.isa_model.endian, + TestDirectiveLines.memzone, + TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model + ) + self.assertIsInstance(t1, PageAlignLine) + t1.set_start_address(3) + self.assertEqual(t1.address, 3) + + t2: LineObject = DirectiveLine.factory( + 1234, + '.align 4', + 'page align', + TestDirectiveLines.isa_model.endian, + TestDirectiveLines.memzone, + TestDirectiveLines.memory_zone_manager, + TestDirectiveLines.isa_model + ) + self.assertIsInstance(t2, PageAlignLine) + t2.set_start_address(3) + self.assertEqual(t2.address, 4) + + local_isa_model = copy.deepcopy(TestDirectiveLines.isa_model) + local_isa_model._config['general']['page_size'] = 8 + t3: LineObject = DirectiveLine.factory( + 1234, + '.align', + 'page align', + TestDirectiveLines.isa_model.endian, + TestDirectiveLines.memzone, + TestDirectiveLines.memory_zone_manager, + local_isa_model + ) + self.assertIsInstance(t3, PageAlignLine) + t3.set_start_address(3) + self.assertEqual(t3.address, 8) + + # test using an expression for the page size + label_values = GlobalLabelScope(['a', 'b', 'sp', 'mar']) + label_values.set_label_value('eight', 8, LineIdentifier(1)) + t4: LineObject = DirectiveLine.factory( + 1234, + '.align (eight >> 1)', + 'page align', + TestDirectiveLines.isa_model.endian, + TestDirectiveLines.memzone, + TestDirectiveLines.memory_zone_manager, + local_isa_model + ) + t4.label_scope = label_values + self.assertIsInstance(t4, PageAlignLine) + t4.set_start_address(3) + self.assertEqual(t4.address, 4) + if __name__ == '__main__': unittest.main() diff --git a/test/test_expression.py b/test/test_expression.py index 6522064..5387c9c 100644 --- a/test/test_expression.py +++ b/test/test_expression.py @@ -230,29 +230,35 @@ def test_negative_values(self): line_id = LineIdentifier(1927, 'test_character_ordinals_in_expressions') self.assertEqual( - parse_expression(line_id, "-21").get_value(TestExpression.label_values, 1), + parse_expression(line_id, '-21').get_value(TestExpression.label_values, 1), -21, - "negative 21" + 'negative 21' ) self.assertEqual( - parse_expression(line_id, "5 * ( -6 )").get_value(TestExpression.label_values, 1), + parse_expression(line_id, '5 * ( -6 )').get_value(TestExpression.label_values, 1), -30, - "negative 30" + 'negative 30' ) self.assertEqual( - parse_expression(line_id, "10 + -(5*2)").get_value(TestExpression.label_values, 1), + parse_expression(line_id, '10 + -(5*2)').get_value(TestExpression.label_values, 1), 0, - "0" + '0' ) self.assertEqual( - parse_expression(line_id, "-2*MAX_N").get_value(TestExpression.label_values, 1), + parse_expression(line_id, '-2*MAX_N').get_value(TestExpression.label_values, 1), -40, - "negative label expression" + 'negative label expression' ) + def test_unknown_expression_parts(self): + line_id = LineIdentifier(1928, 'test_unknown_expression_parts') + + with self.assertRaises(SystemExit, msg='extraneous comparison operator'): + parse_expression(line_id, '<$2024').get_value(TestExpression.label_values, 1) + if __name__ == '__main__': unittest.main() diff --git a/test/test_instruction_parsing.py b/test/test_instruction_parsing.py index 5991ed0..977231c 100644 --- a/test/test_instruction_parsing.py +++ b/test/test_instruction_parsing.py @@ -32,6 +32,31 @@ def setUpClass(cls): def setUp(self): InstructionLine._INSTRUCTUION_EXTRACTION_PATTERN = None + def test_instruction_character_set_parsing(self): + # test instructions with periods in them + fp = pkg_resources.files(config_files).joinpath('test_instructions_with_periods.yaml') + isa_model = AssemblerModel(str(fp), 0) + memzone_mngr = MemoryZoneManager( + isa_model.address_size, + isa_model.default_origin, + isa_model.predefined_memory_zones + ) + + ins1 = InstructionLine.factory( + 22, + ' ma.hl a,[hl+2]', + 'some comment!', + isa_model, + memzone_mngr.global_zone, + memzone_mngr, + ) + ins1.set_start_address(1212) + self.assertIsInstance(ins1, InstructionLine) + self.assertEqual(ins1.byte_size, 2, 'has 2 bytes') + ins1.label_scope = TestInstructionParsing.label_values + ins1.generate_bytes() + self.assertEqual(ins1.get_bytes(), bytearray([0x45, 0x02]), 'instruction should match') + def test_instruction_variant_matching(self): fp = pkg_resources.files(config_files).joinpath('test_instructions_with_variants.yaml') isa_model = AssemblerModel(str(fp), 0) @@ -184,7 +209,7 @@ def test_operand_decorators(self): ins2.generate_bytes() self.assertEqual(list(ins2.get_bytes()), [0xFF, 0x64, 0x1D], 'instruction byte should match') - def test_indexed_regsiter_operands(self): + def test_indexed_register_operands(self): fp = pkg_resources.files(config_files).joinpath('test_indirect_indexed_register_operands.yaml') isa_model = AssemblerModel(str(fp), 0) memzone_mngr = MemoryZoneManager( @@ -312,7 +337,7 @@ def test_label_parsing(self): l1: LineObject = LineOjectFactory.parse_line( lineid, - "LABEL = %00001111", + 'LABEL = %00001111', isa_model, TestInstructionParsing.label_values, memzone_mngr.global_zone, @@ -327,7 +352,7 @@ def test_label_parsing(self): l2: LineObject = LineOjectFactory.parse_line( lineid, - ".local_label:", + '.local_label:', isa_model, TestInstructionParsing.label_values, memzone_mngr.global_zone, @@ -344,7 +369,7 @@ def test_label_parsing(self): l3: LineObject = LineOjectFactory.parse_line( lineid, - "old_style EQU 42H", + 'old_style EQU 42H', isa_model, TestInstructionParsing.label_values, memzone_mngr.global_zone, @@ -356,7 +381,7 @@ def test_label_parsing(self): l3.set_start_address(42) l3.label_scope = TestInstructionParsing.label_values self.assertIsInstance(l3, LabelLine) - self.assertTrue(l3.is_constant, "label should be a constant") + self.assertTrue(l3.is_constant, 'label should be a constant') self.assertEqual(l3.get_value(), 0x42, 'value should be right') def test_operand_bytecode_ordering(self): @@ -788,3 +813,128 @@ def test_valid_address_operand_enforcement(self): self.assertIsInstance(e2, InstructionLine) self.assertEqual(e2.byte_size, 3, 'has 3 bytes') e2.generate_bytes() + + def test_address_operands(self): + from bespokeasm.assembler.model.operand.types.address import AddressByteCodePart + + fp = pkg_resources.files(config_files).joinpath('test_valid_address_enforcement.yaml') + isa_model = AssemblerModel(str(fp), 0) + memzone_mngr = MemoryZoneManager( + isa_model.address_size, isa_model.default_origin, isa_model.predefined_memory_zones, + ) + memzone_32bit_mngr = MemoryZoneManager(32, 0,) + lineid = LineIdentifier(42, 'test_address_operands') + + # first, test argument value generation with no LSB slicing + bc1 = AddressByteCodePart( + '$2020', + 16, + True, + 'little', + lineid, + memzone_mngr.global_zone, + False, + False, + ) + value1 = bc1.get_value(TestInstructionParsing.label_values, 0x2010, 32) + self.assertEqual(value1, 0x2020, 'byte code should match') + + # now, test argument value generation with LSB slicing + bc2 = AddressByteCodePart( + '$2045', + 8, + True, + 'little', + lineid, + memzone_mngr.global_zone, + True, + True, + ) + value2 = bc2.get_value(TestInstructionParsing.label_values, 0x2010, 32) + self.assertEqual(value2, 0x45, 'byte code should match') + + with self.assertRaises(ValueError, msg="address MSBs don't match"): + bc2.get_value(TestInstructionParsing.label_values, 0x2250, 32) + + # test 16-bit slize with a 32-bit address + bc3 = AddressByteCodePart( + '$19458899', + 16, + True, + 'little', + lineid, + memzone_32bit_mngr.global_zone, + True, + True, + ) + value3 = bc3.get_value(TestInstructionParsing.label_values, 0x19451000, 32) + self.assertEqual(value3, 0x8899, 'byte code should match') + with self.assertRaises(ValueError, msg="address MSBs don't match"): + bc3.get_value(TestInstructionParsing.label_values, 0x20241000, 32) + + # test error conditions + # error case: extraneous comparison operators ignored + with self.assertRaises(SystemExit, msg='extraneous comparison operators should be ignored'): + AddressByteCodePart( + '<$2024', + 8, + True, + 'little', + lineid, + memzone_mngr.global_zone, + True, + False, + ) + + # Test byte code generation for sliced addresses + t1 = InstructionLine.factory( + lineid, 'jmp_local $2FF8', 'comment', + isa_model, memzone_mngr.global_zone, memzone_mngr, + ) + t1.set_start_address(0x2F10) + t1.label_scope = TestInstructionParsing.label_values + self.assertIsInstance(t1, InstructionLine) + self.assertEqual(t1.byte_size, 2, 'has 2 bytes') + t1.generate_bytes() + self.assertEqual(list(t1.get_bytes()), [0xE0, 0xF8], 'instruction byte should match') + + # ensure MSBs match + t1.set_start_address(0x3350) + with self.assertRaises(ValueError, msg="address MSBs don't match"): + t1.generate_bytes() + + # Test bye code generation for unsliced addresses + t2 = InstructionLine.factory( + lineid, 'jmpa $2FF8', 'comment', + isa_model, memzone_mngr.global_zone, memzone_mngr, + ) + t2.set_start_address(0x2F10) + t2.label_scope = TestInstructionParsing.label_values + self.assertIsInstance(t2, InstructionLine) + self.assertEqual(t2.byte_size, 3, 'has 3 bytes') + t2.generate_bytes() + self.assertEqual(list(t2.get_bytes()), [0xE1, 0xF8, 0x2F], 'instruction byte should match') + + # test operand address is in target memory zone + t3 = InstructionLine.factory( + lineid, 'jmp_test1 $5150', 'comment', + isa_model, memzone_mngr.global_zone, memzone_mngr, + ) + t3.set_start_address(0x2F10) + t3.label_scope = TestInstructionParsing.label_values + self.assertIsInstance(t3, InstructionLine) + self.assertEqual(t3.byte_size, 3, 'has 3 bytes') + t3.generate_bytes() + self.assertEqual(list(t3.get_bytes()), [0xE2, 0x50, 0x51], 'instruction byte should match') + + # enforce operand address is in target memory zone + t4 = InstructionLine.factory( + lineid, 'jmp_test1 $4425', 'comment', + isa_model, memzone_mngr.global_zone, memzone_mngr, + ) + t4.set_start_address(0x2F10) + t4.label_scope = TestInstructionParsing.label_values + self.assertIsInstance(t4, InstructionLine) + self.assertEqual(t4.byte_size, 3, 'has 3 bytes') + with self.assertRaises(SystemExit, msg='address not in target memory zone'): + t4.generate_bytes() diff --git a/test/test_line_objects.py b/test/test_line_objects.py index 53bc693..a043ec5 100644 --- a/test/test_line_objects.py +++ b/test/test_line_objects.py @@ -8,12 +8,13 @@ from bespokeasm.assembler.line_object.data_line import DataLine from bespokeasm.assembler.line_object.label_line import LabelLine, is_valid_label from bespokeasm.assembler.line_object.instruction_line import InstructionLine -from bespokeasm.assembler.line_object.directive_line import AddressOrgLine +from bespokeasm.assembler.line_object.directive_line.address import AddressOrgLine from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager from bespokeasm.assembler.memory_zone import MemoryZone from bespokeasm.assembler.preprocessor import Preprocessor from bespokeasm.assembler.preprocessor.condition_stack import ConditionStack +from bespokeasm.assembler.line_object.emdedded_string import EmbeddedString from test import config_files @@ -768,6 +769,109 @@ def test_unknown_instruction(self): 0, ) + def test_embedded_string_lines(self): + fp = pkg_resources.files(config_files).joinpath('test_operand_features.yaml') + isa_model = AssemblerModel(str(fp), 0) + # force embedded strings to be allowed + isa_model._config['general']['allow_embedded_strings'] = True + memzone_mngr = MemoryZoneManager( + isa_model.address_size, + isa_model.default_origin, + isa_model.predefined_memory_zones, + ) + + lineid = LineIdentifier(66, 'test_embedded_string_lines') + + # test creation of embedded string object + t1 = EmbeddedString.factory( + lineid, + '"this is a test" nop', + 'embedded string', + memzone_mngr.global_zone, + 0, + ) + self.assertIsInstance(t1, EmbeddedString) + self.assertEqual(t1.byte_size, 15, 'string has 15 bytes') + t1.generate_bytes() + self.assertEqual(t1.get_bytes(), bytearray([ord(c) for c in 'this is a test\x00']), 'string matches') + self.assertEqual(t1.instruction, '"this is a test"', 'instruction string matches') + + # test the non-creation of an embedded string object + t2 = EmbeddedString.factory( + lineid, + 'this is a test', + 'embedded string', + memzone_mngr.global_zone, + 0, + ) + self.assertIsNone(t2, 'no embedded string object created') + + # test the line object creation for a complext line with an embedded string + + lo1: list[LineObject] = LineOjectFactory.parse_line( + lineid, + 'add 5 "this is a test" nop', + isa_model, + TestLineObject.label_values, + memzone_mngr.global_zone, + memzone_mngr, + Preprocessor(), + ConditionStack(), + 0, + ) + self.assertEqual(len(lo1), 3, 'There should be 3 parsed instructions') + self.assertIsInstance(lo1[0], InstructionLine) + self.assertIsInstance(lo1[1], EmbeddedString) + self.assertIsInstance(lo1[2], InstructionLine) + self.assertEqual(lo1[1].byte_size, 15, 'string has 15 bytes') + for lo in lo1: + lo.generate_bytes() + self.assertEqual(lo1[1].get_bytes(), bytearray([ord(c) for c in 'this is a test\x00']), 'string matches') + + # test that when embedded strings are not allowed, the embedded string is not created + isa_model._config['general']['allow_embedded_strings'] = False + with self.assertRaises(SystemExit, msg='embedded strings should not be allowed'): + LineOjectFactory.parse_line( + lineid, + 'add 5 "this is a test" nop', + isa_model, + TestLineObject.label_values, + memzone_mngr.global_zone, + memzone_mngr, + Preprocessor(), + ConditionStack(), + 0, + ) + + def test_embedded_string_bugs(self): + fp = pkg_resources.files(config_files).joinpath('test_operand_features.yaml') + isa_model = AssemblerModel(str(fp), 0) + # force embedded strings to be allowed + isa_model._config['general']['allow_embedded_strings'] = True + memzone_mngr = MemoryZoneManager( + isa_model.address_size, + isa_model.default_origin, + isa_model.predefined_memory_zones, + ) + + lineid = LineIdentifier(67, 'test_embedded_string_bugs') + # test bug where length of embedded string was not being calculated correctly + # escapes sequences in code files are double escaped when read in, so the + # string "\n" is read as "\\n". The byte conversion that is done will properly + # convert the string to the escaped value, but the bug was we were taking the + # length of the string before the escape sequences were converted, so the length + # of the string was 3 instead of 2. + t1 = EmbeddedString.factory( + lineid, + '"\\n"', + 'embedded string', + memzone_mngr.global_zone, + 0, + ) + self.assertIsNotNone(t1, 'embedded string object created') + self.assertIsInstance(t1, EmbeddedString) + self.assertEqual(t1.byte_size, 2, 'string has 2 bytes') + if __name__ == '__main__': unittest.main() diff --git a/test/test_preprocessor_symbols.py b/test/test_preprocessor_symbols.py index c7afb4f..9a963ff 100644 --- a/test/test_preprocessor_symbols.py +++ b/test/test_preprocessor_symbols.py @@ -7,7 +7,8 @@ from bespokeasm.assembler.preprocessor import Preprocessor from bespokeasm.assembler.preprocessor.condition import \ IfPreprocessorCondition, IfdefPreprocessorCondition, ElifPreprocessorCondition, \ - ElsePreprocessorCondition, EndifPreprocessorCondition + ElsePreprocessorCondition, EndifPreprocessorCondition, MutePreprocessorCondition, \ + UnmutePreprocessorCondition from bespokeasm.assembler.line_identifier import LineIdentifier from bespokeasm.assembler.model import AssemblerModel from bespokeasm.assembler.memory_zone.manager import MemoryZoneManager @@ -52,7 +53,7 @@ def __init__(self, line: LineIdentifier): super().__init__('#if true', line) def __repr__(self) -> str: - return "MockPreprocessorCondition_True" + return 'MockPreprocessorCondition_True' def evaluate(self, preprocessor: Preprocessor) -> bool: return True @@ -62,7 +63,7 @@ def __init__(self, line: LineIdentifier): super().__init__('#if false', line) def __repr__(self) -> str: - return "MockPreprocessorCondition_False" + return 'MockPreprocessorCondition_False' def evaluate(self, preprocessor: Preprocessor) -> bool: return False @@ -174,7 +175,7 @@ def test_define_symbol_line_objects(self): l1: LineObject = LineOjectFactory.parse_line( lineid, - "#define TEST_SYMBOL 0x1234", + '#define TEST_SYMBOL 0x1234', isa_model, global_scope, memzone_mngr.global_zone, @@ -323,25 +324,81 @@ def test_condition_stack(self): # add an if condition that should be false c1 = IfPreprocessorCondition('#if s1 < 50', LineIdentifier('test_condition_stack', 1)) - stack.process_condition(c1) + stack.process_condition(c1, preprocessor) self.assertFalse(stack.currently_active(preprocessor), 'condition should be False') # add an elif condition that should be true c2 = ElifPreprocessorCondition('#elif s1 >= 55', LineIdentifier('test_condition_stack', 2)) - stack.process_condition(c2) + stack.process_condition(c2, preprocessor) self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') # add an elif that could be true but is false because it follows a true elif c3 = ElifPreprocessorCondition('#elif s1 < 60', LineIdentifier('test_condition_stack', 3)) - stack.process_condition(c3) + stack.process_condition(c3, preprocessor) self.assertFalse(stack.currently_active(preprocessor), 'condition should be False') # add the else condition c4 = ElsePreprocessorCondition('#else', LineIdentifier('test_condition_stack', 4)) - stack.process_condition(c4) + stack.process_condition(c4, preprocessor) self.assertFalse(stack.currently_active(preprocessor), 'condition should be False') # add endif c5 = EndifPreprocessorCondition('#endif', LineIdentifier('test_condition_stack', 5)) - stack.process_condition(c5) + stack.process_condition(c5, preprocessor) self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') + + def test_muting(self): + stack = ConditionStack() + preprocessor = Preprocessor() + + self.assertFalse(stack.is_muted, 'initial condition should be False') + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 1)), preprocessor) + self.assertTrue(stack.is_muted, 'condition should be True') + stack.process_condition(UnmutePreprocessorCondition('#unmute', LineIdentifier('test_muting', 2)), preprocessor) + self.assertFalse(stack.is_muted, 'condition should be False') + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 3)), preprocessor) + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 4)), preprocessor) + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 5)), preprocessor) + self.assertTrue(stack.is_muted, 'condition should be True') + stack.process_condition(UnmutePreprocessorCondition('#unmute', LineIdentifier('test_muting', 6)), preprocessor) + self.assertTrue(stack.is_muted, 'condition should be True') + stack.process_condition(UnmutePreprocessorCondition('#unmute', LineIdentifier('test_muting', 7)), preprocessor) + self.assertTrue(stack.is_muted, 'condition should be True') + stack.process_condition(UnmutePreprocessorCondition('#unmute', LineIdentifier('test_muting', 8)), preprocessor) + self.assertFalse(stack.is_muted, 'condition should be False') + + def test_conditional_muting(self): + """Demonstrate that conditional compilation controls the mute/unmute preprocessor directives.""" + # if a #mute is inside a false condition, it should not mute + stack = ConditionStack() + preprocessor = Preprocessor() + preprocessor.create_symbol('s1', '57') + preprocessor.create_symbol('s2', 's1*2') + + self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') + self.assertFalse(stack.is_muted, 'mute should be False') + + c1 = IfPreprocessorCondition('#if s1 < 50', LineIdentifier('test_condition_stack', 1)) + stack.process_condition(c1, preprocessor) + self.assertFalse(stack.currently_active(preprocessor), 'condition should be False') + self.assertFalse(stack.is_muted, 'mute should be False') + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 2)), preprocessor) + self.assertFalse(stack.currently_active(preprocessor), 'condition should be False') + self.assertFalse(stack.is_muted, 'mute should be False') + + # if a #mute is inside a true condition, it should mute + c2 = ElsePreprocessorCondition('#else', LineIdentifier('test_condition_stack', 3)) + stack.process_condition(c2, preprocessor) + self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') + self.assertFalse(stack.is_muted, 'mute should be False') + stack.process_condition(MutePreprocessorCondition('#mute', LineIdentifier('test_muting', 4)), preprocessor) + self.assertTrue(stack.currently_active(preprocessor), 'condition should be False') + self.assertTrue(stack.is_muted, 'mute should be True') + + c3 = EndifPreprocessorCondition('#endif', LineIdentifier('test_condition_stack', 5)) + stack.process_condition(c3, preprocessor) + self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') + self.assertTrue(stack.is_muted, 'mute should be True') + stack.process_condition(UnmutePreprocessorCondition('#unmute', LineIdentifier('test_muting', 6)), preprocessor) + self.assertTrue(stack.currently_active(preprocessor), 'condition should be True') + self.assertFalse(stack.is_muted, 'mute should be False') diff --git a/test/test_utilities.py b/test/test_utilities.py index 8021799..1b7ad2c 100644 --- a/test/test_utilities.py +++ b/test/test_utilities.py @@ -35,7 +35,7 @@ def test_is_string_numeric(self): self.assertTrue(is_string_numeric('08FH'), 'string is numeric (hexadecimal)') self.assertTrue(is_string_numeric("'1'"), 'string is numeric (character ordinal)') self.assertFalse(is_string_numeric("'12'"), 'character ordinal can only be one character long') - self.assertFalse(is_string_numeric("0b10011001"), 'binary numbers do not strt with "0b"') + self.assertFalse(is_string_numeric('0b10011001'), 'binary numbers do not strt with "0b"') def test_PackedBits(self): ib1 = PackedBits()