6502SimDesktop/app/lib/64spec.asm

1195 lines
39 KiB
NASM

.importonce
// The MIT License (MIT)
//
// Copyright (c) 2015 Michał Taszycki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
.const _64SPEC_VERSION_MAJOR = 0
.const _64SPEC_VERSION_MINOR = 5
.const _64SPEC_VERSION_PATCH = 0
.function _64spec_version() {
.return "" + _64SPEC_VERSION_MAJOR + '.' + _64SPEC_VERSION_MINOR + '.' + _64SPEC_VERSION_PATCH
}
.const _TEXT_COLOR = $0286
.const _BORDER = $d020
.const _BACKGROUND = $d021
.const _CHROUT = $ffd2
.const _CHKOUT = $FFC9
.const _PLOT = $fff0
.const _SETLFS = $FFBA
.const _OPEN = $FFC0
.const _CLOSE = $FFC3
.const _SETNAM = $FFBD
.const _CLRCHN = $FFCC
.const _CLRSCR = $e544
.const _PRINTWORD = $bdcd
.const _CR=13
.const _CLS = 147
.const _UPPERCASE = 142
.const _LOWERCASE = 14
.struct _64SPEC_CONFIG {
print_header,
clear_screen_at_initialization,
change_character_set,
on_exit,
success_color,
failure_color,
change_text_color,
change_text_color_on_final_result,
change_text_color_on_immediate_result,
text_color,
revert_to_initial_text_color,
change_background_color,
change_background_color_on_final_result,
background_color,
change_border_color,
border_color,
change_border_color_on_final_result,
print_immediate_result,
immediate_result_success_character,
immediate_result_failure_character,
print_final_results,
write_final_results_to_file,
result_file_name,
assertion_passed_subroutine,
assertion_failed_subroutine,
result_all_passed_message,
result_some_passed_message,
result_all_failed_message,
print_configuration,
print_command_line_options,
print_context_description,
print_example_description,
change_context_description_text_color,
change_example_description_text_color,
print_context_results,
print_example_results,
_use_custom_result_all_passed_message,
_use_custom_result_some_passed_message,
_use_custom_result_all_failed_message,
_use_custom_assertion_passed_subroutine,
_use_custom_assertion_failed_subroutine
}
// Default configuration
.const _64SPEC = _64SPEC_CONFIG()
.eval config_64spec("print_header", true)
.eval config_64spec("clear_screen_at_initialization", true)
.eval config_64spec("change_character_set", "lowercase")
.eval config_64spec("on_exit", "rts")
.eval config_64spec("success_color", GREEN)
.eval config_64spec("failure_color", RED)
.eval config_64spec("change_text_color", true)
.eval config_64spec("change_text_color_on_immediate_result", true)
.eval config_64spec("change_text_color_on_final_result", true)
.eval config_64spec("text_color", DARK_GRAY)
.eval config_64spec("revert_to_initial_text_color", false)
.eval config_64spec("change_background_color", true)
.eval config_64spec("change_background_color_on_final_result", false)
.eval config_64spec("background_color", BLACK)
.eval config_64spec("change_border_color", true)
.eval config_64spec("change_border_color_on_final_result", true)
.eval config_64spec("border_color", BLACK)
.eval config_64spec("print_context_description", true)
.eval config_64spec("print_example_description", true)
// TODO: Fix color printing when screen scrolls, and change defaults to true.
.eval config_64spec("change_context_description_text_color", false)
.eval config_64spec("change_example_description_text_color", false)
.eval config_64spec("print_context_results", false)
.eval config_64spec("print_example_results", false)
.eval config_64spec("print_immediate_result", true)
.eval config_64spec("immediate_result_success_character", "default")
.eval config_64spec("immediate_result_failure_character", "default")
.eval config_64spec("print_final_results", true)
.eval config_64spec("write_final_results_to_file", false)
.eval config_64spec("result_file_name", "result.txt")
.eval config_64spec("print_configuration", false)
.eval config_64spec("print_command_line_options", false)
// Overridable addresses. Set at initialization time.
.eval config_64spec("assertion_passed_subroutine", "default")
.eval config_64spec("assertion_failed_subroutine", "default")
.eval config_64spec("result_all_passed_message", "default")
.eval config_64spec("result_some_passed_message", "default")
.eval config_64spec("result_all_failed_message", "default")
// Custom memory markers.
// Some labels cannot be resolved in the first pass and we can't use them in .if statements.
// Therefore additional boolean variables are used to signify if user customized an address.
.eval _64SPEC.set("_use_custom_result_all_passed_message", false)
.eval _64SPEC.set("_use_custom_result_some_passed_message", false)
.eval _64SPEC.set("_use_custom_result_all_failed_message", false)
.eval _64SPEC.set("_use_custom_assertion_passed_subroutine", false)
.eval _64SPEC.set("_use_custom_assertion_failed_subroutine", false)
.function config_64spec(key, value) {
.if (validate_boolean_option("print_header", key, value)) .return
.if (validate_boolean_option("clear_screen_at_initialization", key, value)) .return
.if (validate_boolean_option("change_text_color", key, value)) .return
.if (validate_boolean_option("change_text_color_on_immediate_result", key, value)) .return
.if (validate_boolean_option("change_text_color_on_final_result", key, value)) .return
.if (validate_boolean_option("revert_to_initial_text_color", key, value)) .return
.if (validate_boolean_option("change_background_color", key, value)) .return
.if (validate_boolean_option("change_background_color_on_final_result", key, value)) .return
.if (validate_boolean_option("change_border_color", key, value)) .return
.if (validate_boolean_option("change_border_color_on_final_result", key, value)) .return
.if (validate_boolean_option("print_immediate_result", key, value)) .return
.if (validate_boolean_option("print_final_results", key, value)) .return
.if (validate_boolean_option("write_final_results_to_file", key, value)) .return
.if (validate_boolean_option("print_configuration", key, value)) .return
.if (validate_boolean_option("print_command_line_options", key, value)) .return
.if (validate_boolean_option("print_context_description", key, value)) .return
.if (validate_boolean_option("print_example_description", key, value)) .return
.if (validate_boolean_option("change_context_description_text_color", key, value)) .return
.if (validate_boolean_option("change_example_description_text_color", key, value)) .return
.if (validate_boolean_option("print_context_results", key, value)) .return
.if (validate_boolean_option("print_example_results", key, value)) .return
.if (validate_color_option("success_color", key, value)) .return
.if (validate_color_option("failure_color", key, value)) .return
.if (validate_color_option("text_color", key, value)) .return
.if (validate_color_option("background_color", key, value)) .return
.if (validate_color_option("border_color", key, value)) .return
.if (validate_character_option("immediate_result_success_character", key, value)) .return
.if (validate_character_option("immediate_result_failure_character", key, value)) .return
.if (validate_non_empty_string_option("result_file_name", key, value)) .return
.if (validate_set_option("change_character_set", List().add(
_64SPEC_SET_OPTION("\"lowercase\"", "lowercase"),
_64SPEC_SET_OPTION("\"uppercase\"", "uppercase"),
_64SPEC_SET_OPTION("false", false)
), key, value)) .return
.if (validate_set_option("on_exit", List().add(
_64SPEC_SET_OPTION("\"rts\"", "rts"),
_64SPEC_SET_OPTION("\"loop\"", "loop"),
_64SPEC_SET_OPTION("\"jam\"", "jam")
), key, value)) .return
.if (mark_custom_memory_address_option("assertion_passed_subroutine", key, value)) .return
.if (mark_custom_memory_address_option("assertion_failed_subroutine", key, value)) .return
.if (mark_custom_memory_address_option("result_all_passed_message", key, value)) .return
.if (mark_custom_memory_address_option("result_some_passed_message", key, value)) .return
.if (mark_custom_memory_address_option("result_all_failed_message", key, value)) .return
.error "Unrecognized _64SPEC configuration option - \"" + key + "\""
}
.function mark_custom_memory_address_option(expected_key, key, value) {
.if (key != expected_key) .return false
.eval _64SPEC.set("_use_custom_" + expected_key, true)
.eval _64SPEC.set(key, value)
.return true
}
.function validate_color_option(expected_key, key, value) {
.if (key != expected_key) .return false
.if(value < 0 || value > 15) {
.error "_64SPEC configuration option - \"" + expected_key + "\" has to be a valid color index in a range [0..15]."
}
.eval _64SPEC.set(key, value)
.return true
}
.function validate_boolean_option(expected_key, key, value) {
.if (key != expected_key) .return false
.if (value != true && value != false) {
.error "_64SPEC configuration option - \"" + expected_key + "\" has to be either be true or false."
}
.eval _64SPEC.set(key, value)
.return true
}
.function validate_non_empty_string_option(expected_key, key, value) {
.if (key != expected_key) .return false
.if (value == "") {
.error "_64SPEC configuration option - \"" + expected_key + "\" cannot be an empty string."
}
.eval _64SPEC.set(key, value)
.return true
}
.function validate_character_option(expected_key, key, value) {
.if (key != expected_key) .return false
.if (value != "default" && [value < 0 || value > 255]) {
.error "_64SPEC configuration option - \"" + expected_key + "\" has to be a one byte value representing PETSCII character or \"default\"."
}
.eval _64SPEC.set(key, value)
.return true
}
.struct _64SPEC_SET_OPTION {name, value}
.function validate_set_option(expected_key, allowed_values, key, value) {
.if (key != expected_key) .return false
.var options_string = ""
.for (var i = 0; i < allowed_values.size(); i++) {
.eval options_string += allowed_values.get(i).name
.if (i < allowed_values.size() - 1) {
.eval options_string += ", "
}
.if (value == allowed_values.get(i).value) {
.eval _64SPEC.set(key, value)
.return true
}
}
.error "_64SPEC configuration option - \"" + expected_key + "\" has to be a one of: " + options_string
}
.macro init_spec() {
.if (cmdLineVars.containsKey("on_exit")) {
.eval _64SPEC.set("on_exit", cmdLineVars.get("on_exit"))
}
.if (cmdLineVars.containsKey("write_final_results_to_file")) {
.eval _64SPEC.set("write_final_results_to_file", cmdLineVars.get("write_final_results_to_file").asBoolean())
}
.if (cmdLineVars.containsKey("result_file_name")) {
.eval _64SPEC.set("result_file_name", cmdLineVars.get("result_file_name"))
}
.if (_64SPEC.immediate_result_failure_character == "default") {
.eval _64SPEC.set("immediate_result_failure_character", [_64SPEC.change_character_set == "lowercase"] ? _64spec_scr_to_pet('x') : _64spec_scr_to_pet('x'))
}
.if (_64SPEC.immediate_result_success_character == "default") {
.eval _64SPEC.set("immediate_result_success_character", [_64SPEC.change_character_set == "lowercase"] ? _64spec_scr_to_pet('.') : _64spec_scr_to_pet('.'))
}
.if (_64SPEC.print_configuration) {
.print "64Spec Configuration:"
.for (var i = 0;i < _64SPEC.getNoOfFields(); i++) {
.print " " + _64SPEC.getFieldNames().get(i) + " = " +_64SPEC.get(i)
}
}
.if (_64SPEC.print_command_line_options) {
.print "Command Line Options:"
.for (var i = 0;i < cmdLineVars.keys().size(); i++) {
.var key = cmdLineVars.keys().get(i)
.print " " + key + " = " + cmdLineVars.get(key)
}
}
:BasicUpstart2(tests_init)
.pc = * "Tests Data"
_version_major:
.byte _64SPEC_VERSION_MAJOR
_version_minor:
.byte _64SPEC_VERSION_MINOR
_version_patch:
.byte _64SPEC_VERSION_PATCH
_total_assertions_count:
.word 0
_passed_assertions_count:
.word 0
_final_tests_result:
.word 0
_stored_a:
.byte 0
_stored_x:
.byte 0
_stored_y:
.byte 0
_stored_p:
.byte 0
_initial_text_color:
.if (_64SPEC.change_text_color && _64SPEC.revert_to_initial_text_color) {
.byte 0
}
_header:
.if (_64SPEC.print_header) {
.var lines = List()
.if (_64SPEC.change_character_set == "lowercase") {
.eval lines.add("****** 64spec v" + _64spec_version() + " ******")
.eval lines.add("Testing Framework by Michal Taszycki")
.eval lines.add("Docs at http://64bites.com/64spec")
} else {
.eval lines.add("****** 64spec v" + _64spec_version() + " ******")
.eval lines.add("testing framework by michal taszycki")
.eval lines.add("docs at http://64bites.com/64spec")
}
.byte _CR
.for (var i = 0; i < lines.size(); i++) {
.fill [40 - lines.get(i).size()] / 2, ' '
:_64spec_pet_text(lines.get(i))
.byte _CR
.byte _CR
}
.byte 0
}
.if(!_64SPEC._use_custom_result_all_failed_message) {
.eval _64SPEC.set("result_all_failed_message", *)
.if (_64SPEC.change_character_set == "lowercase") {
:_64spec_pet_text("All Tests FAILED: ")
} else {
:_64spec_pet_text("all tests failed: ")
}
.byte 0
}
.if(!_64SPEC._use_custom_result_some_passed_message) {
.eval _64SPEC.set("result_some_passed_message", *)
.if (_64SPEC.change_character_set == "lowercase") {
:_64spec_pet_text("Some tests PASSED: ")
} else {
:_64spec_pet_text("some tests passed: ")
}
.byte 0
}
.if(!_64SPEC._use_custom_result_all_passed_message) {
.eval _64SPEC.set("result_all_passed_message", *)
.if (_64SPEC.change_character_set == "lowercase") {
:_64spec_pet_text("All tests PASSED: ")
} else {
:_64spec_pet_text("all tests passed: ")
}
.byte 0
}
_last_context:
.if (_64SPEC.print_context_description) {
.word 0 // text pointer
.word 0 // cursor position
.word 0 // total assertions count
.word 0 // passed assertions count
.byte 0 // tests result
}
_last_example:
.if (_64SPEC.print_example_description) {
.word 0 // text pointer
.word 0 // cursor position
.word 0 // total assertions count
.word 0 // passed assertions count
.byte 0 // tests result
}
_description_data:
.if (_64SPEC.print_context_description || _64SPEC.print_example_description) {
.word 0 // cursor position
.byte 0 // flags - 7 cleared - first context, 6 cleared - first example
}
.pc = * "Tests Subroutines"
.if(!_64SPEC._use_custom_assertion_passed_subroutine) {
.eval _64SPEC.set("assertion_passed_subroutine", *)
:_assertion_passed()
rts
}
.if(!_64SPEC._use_custom_assertion_failed_subroutine) {
.eval _64SPEC.set("assertion_failed_subroutine", *)
:_assertion_failed()
rts
}
_print_string:
:_print_string($ffff)
rts
.pc = * "Test Initialization"
tests_init:
.if (_64SPEC.clear_screen_at_initialization) {
:_print_char #_CLS
}
.if (_64SPEC.change_character_set != false) {
:_print_char #[[_64SPEC.change_character_set == "lowercase"] ? _LOWERCASE : _UPPERCASE]
}
.if (_64SPEC.change_text_color && _64SPEC.revert_to_initial_text_color) {
:_64spec_mov _TEXT_COLOR; _initial_text_color
}
:_set_text_color #_64SPEC.text_color
.if (_64SPEC.change_background_color) {
:_64spec_mov #_64SPEC.background_color; _BACKGROUND
}
.if (_64SPEC.change_border_color) {
:_64spec_mov #_64SPEC.border_color; _BORDER
}
.if (_64SPEC.print_header) {
:_print_string #sfspec._header
}
:_reset_tests_result(sfspec._total_assertions_count)
.pc = * "Specification"
specification:
}
.macro finish_spec() {
.pc = * "Spec Results Rendering"
:_finalize_last_context()
:_finalize_last_example()
:render_results()
.if (_64SPEC.revert_to_initial_text_color) {
:_set_text_color sfspec._initial_text_color
} else {
:_set_text_color #_64SPEC.text_color
}
.if (_64SPEC.on_exit == "rts") {
rts
} else .if (_64SPEC.on_exit == "loop") {
end:
jmp end
} else /* jam */ {
.byte $02
}
}
.macro _assertion_failed() {
:_64spec_inc16 sfspec._total_assertions_count
.if (_64SPEC.print_context_description) {
:_64spec_inc16 sfspec._last_context + 4
}
.if (_64SPEC.print_example_description) {
:_64spec_inc16 sfspec._last_example + 4
}
.if (_64SPEC.print_immediate_result) {
.if (_64SPEC.change_text_color_on_immediate_result) {
:_set_text_color #_64SPEC.failure_color
}
:_print_char #_64SPEC.immediate_result_failure_character
}
}
.macro _assertion_passed() {
:_64spec_inc16 sfspec._passed_assertions_count
:_64spec_inc16 sfspec._total_assertions_count
.if (_64SPEC.print_context_description) {
:_64spec_inc16 sfspec._last_context + 4
:_64spec_inc16 sfspec._last_context + 6
}
.if (_64SPEC.print_example_description) {
:_64spec_inc16 sfspec._last_example + 4
:_64spec_inc16 sfspec._last_example + 6
}
.if (_64SPEC.print_immediate_result) {
.if (_64SPEC.change_text_color_on_immediate_result) {
:_set_text_color #_64SPEC.success_color
}
:_print_char #_64SPEC.immediate_result_success_character
}
}
.macro _reset_tests_result(base_address) {
.var total_assertions_count = base_address
.var passed_assertions_count = base_address + 2
:_64spec_mov16 #$0000; total_assertions_count
:_64spec_mov16 #$0000; passed_assertions_count
}
.macro _calculate_tests_result(base_address) {
.var total_assertions_count = base_address
.var passed_assertions_count = base_address + 2
.var final_tests_result = base_address + 4
lda total_assertions_count
cmp passed_assertions_count
bne !fail+
lda total_assertions_count + 1
cmp passed_assertions_count + 1
bne !fail+
!pass:
// We are "overflowing" with success
lda #%01000000
sta final_tests_result
jmp !end+
!fail:
lda passed_assertions_count
bne !incomplete_fail+
lda passed_assertions_count + 1
bne !incomplete_fail+
!complete_fail:
// We are "not overflowing" with success
lda #%00000000
sta final_tests_result
jmp !end+
!incomplete_fail:
// This is "MInor" failure
lda #%10000000
sta final_tests_result
!end:
}
.macro render_results() {
:_calculate_tests_result(sfspec._total_assertions_count)
:_set_screen_colors()
:_change_text_color_on_final_result()
.if (_64SPEC.print_final_results) {
:_print_final_results()
}
.if (_64SPEC.write_final_results_to_file) {
:_write_final_results_to_file()
}
}
.macro _write_final_results_to_file() {
:_64spec_open_file_for_writing(_64SPEC.result_file_name, 13)
:_64spec_set_file_output(13)
:_print_final_results()
:_64spec_close_file(13)
:_64spec_set_screen_output()
}
.macro _change_text_color_on_final_result() {
.if (_64SPEC.change_text_color_on_final_result) {
bit sfspec._final_tests_result
bvs success
failure:
:_set_text_color #_64SPEC.failure_color
jmp end
success:
:_set_text_color #_64SPEC.success_color
end:
} else {
:_set_text_color #_64SPEC.text_color
}
}
.macro _set_screen_colors() {
.if ([_64SPEC.change_border_color && _64SPEC.change_border_color_on_final_result] || [_64SPEC.change_background_color && _64SPEC.change_background_color_on_final_result]) {
bit sfspec._final_tests_result
bvs success
failure:
lda #_64SPEC.failure_color
jmp end
success:
lda #_64SPEC.success_color
end:
.if (_64SPEC.change_border_color && _64SPEC.change_border_color_on_final_result) {
sta _BORDER
}
.if (_64SPEC.change_background_color && _64SPEC.change_background_color_on_final_result) {
sta _BACKGROUND
}
}
}
.macro _print_result_numbers(base_address) {
.var total_assertions_count = base_address
.var passed_assertions_count = base_address + 2
:_print_char #'('
:_print_int16 passed_assertions_count
:_print_char #'/'
:_print_int16 total_assertions_count
:_print_char #')'
:_print_char #_CR
}
.macro _print_final_results() {
:_print_char #_CR
bit sfspec._final_tests_result
bvs success
bmi partial_failure
failure:
:_print_string #_64SPEC.result_all_failed_message
jmp end
partial_failure:
:_print_string #_64SPEC.result_some_passed_message
jmp end
success:
:_print_string #_64SPEC.result_all_passed_message
end:
:_print_result_numbers(sfspec._total_assertions_count)
}
// Assertions
.pseudocommand assert_i_cleared pass_subroutine; fail_subroutine {
:assert_i_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_i_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%00000100; _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine); _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine)
}
.pseudocommand assert_d_cleared pass_subroutine; fail_subroutine {
:assert_d_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_d_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%00001000; _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine); _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine)
}
.pseudocommand assert_p_has_masked_bits_cleared mask; pass_subroutine; fail_subroutine {
:assert_masked_bits_cleared sfspec._stored_p; mask; pass_subroutine; fail_subroutine
}
.pseudocommand assert_p_has_masked_bits_set mask; pass_subroutine; fail_subroutine {
:assert_masked_bits_set sfspec._stored_p; mask; pass_subroutine; fail_subroutine
}
.pseudocommand assert_masked_bits_cleared actual; mask; pass_subroutine; fail_subroutine {
:_store_state()
lda actual
eor $ff
and mask
bne pass_or_fail.fail
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
:_restore_state()
}
.pseudocommand assert_masked_bits_set actual; mask; pass_subroutine; fail_subroutine {
:_store_state()
lda actual
and mask
cmp mask
bne pass_or_fail.fail
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
:_restore_state()
}
.pseudocommand assert_p_equal expected; pass_subroutine; fail_subroutine {
:assert_equal sfspec._stored_p; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_v_cleared pass_subroutine; fail_subroutine {
:assert_v_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_v_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%01000000; pass_subroutine; fail_subroutine
}
.pseudocommand assert_n_cleared pass_subroutine; fail_subroutine {
:assert_n_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_n_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%10000000; pass_subroutine; fail_subroutine
}
.pseudocommand assert_c_cleared pass_subroutine; fail_subroutine {
:assert_c_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_c_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%00000001; pass_subroutine; fail_subroutine
}
.pseudocommand assert_z_cleared pass_subroutine; fail_subroutine {
:assert_z_set _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_z_set pass_subroutine; fail_subroutine {
:assert_p_has_masked_bits_set #%00000010; pass_subroutine; fail_subroutine
}
.pseudocommand assert_y_not_zero pass_subroutine; fail_subroutine {
:assert_y_zero _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_y_zero pass_subroutine; fail_subroutine {
:assert_y_equal #0; pass_subroutine; fail_subroutine
}
.pseudocommand assert_y_not_equal expected; pass_subroutine; fail_subroutine {
:assert_y_equal expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_y_equal expected; pass_subroutine; fail_subroutine {
:assert_equal sfspec._stored_y; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_x_not_zero pass_subroutine; fail_subroutine {
:assert_x_zero _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_x_zero pass_subroutine; fail_subroutine {
:assert_x_equal #0; pass_subroutine; fail_subroutine
}
.pseudocommand assert_x_not_equal expected; pass_subroutine; fail_subroutine {
:assert_x_equal expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_x_equal expected; pass_subroutine; fail_subroutine {
:assert_equal sfspec._stored_x; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_a_not_zero pass_subroutine; fail_subroutine {
:assert_a_zero _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_a_zero pass_subroutine; fail_subroutine {
:assert_a_equal #0; pass_subroutine; fail_subroutine
}
.pseudocommand assert_a_not_equal expected; pass_subroutine; fail_subroutine {
:assert_a_equal expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_a_equal expected; pass_subroutine; fail_subroutine {
:assert_equal sfspec._stored_a; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_not_equal actual; expected; pass_subroutine; fail_subroutine {
:assert_equal actual; expected;_given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_not_equal16 actual; expected; pass_subroutine; fail_subroutine {
:assert_equal16 actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_not_equal24 actual; expected; pass_subroutine; fail_subroutine {
:assert_equal24 actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_not_equal32 actual; expected; pass_subroutine; fail_subroutine {
:assert_equal32 actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.macro assert_bytes_not_equal(bytes_count, actual, expected, pass_subroutine, fail_subroutine) {
:assert_bytes_equal(bytes_count, actual, expected, _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine), _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine))
}
.pseudocommand assert_bytes_not_equal bytes_count; actual; expected; pass_subroutine; fail_subroutine {
:assert_bytes_equal bytes_count; actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_equal actual; expected; pass_subroutine; fail_subroutine {
:_assert_equal _bits_to_bytes(8); actual; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_equal16 actual; expected; pass_subroutine; fail_subroutine {
:_assert_equal _bits_to_bytes(16); actual; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_equal24 actual; expected; pass_subroutine; fail_subroutine {
:_assert_equal _bits_to_bytes(24); actual; expected; pass_subroutine; fail_subroutine
}
.pseudocommand assert_equal32 actual; expected; pass_subroutine; fail_subroutine {
:_assert_equal _bits_to_bytes(32); actual; expected; pass_subroutine; fail_subroutine
}
.pseudocommand _assert_equal bytes_count; actual; expected; pass_subroutine; fail_subroutine {
:_store_state()
.for (var byte_id = 0; byte_id < bytes_count.getValue(); byte_id++) {
lda _64spec_extract_byte_argument(actual, byte_id)
cmp _64spec_extract_byte_argument(expected, byte_id)
bne pass_or_fail.fail
}
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
:_restore_state()
}
.pseudocommand assert_unsigned_greater_or_equal actual; expected; pass_subroutine; fail_subroutine {
:assert_unsigned_less actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_unsigned_less actual; expected; pass_subroutine; fail_subroutine {
:_store_state()
lda actual
cmp expected
bcs pass_or_fail.fail
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
:_restore_state()
}
.pseudocommand assert_unsigned_less_or_equal actual; expected; pass_subroutine; fail_subroutine {
:assert_unsigned_greater actual; expected; _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine); _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
}
.pseudocommand assert_unsigned_greater actual; expected; pass_subroutine; fail_subroutine {
:_store_state()
lda actual
cmp expected
bcc pass_or_fail.fail
beq pass_or_fail.fail
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
:_restore_state()
}
.macro _store_state() {
php
sta sfspec._stored_a
stx sfspec._stored_x
sty sfspec._stored_y
pla
sta sfspec._stored_p
}
.macro _restore_state() {
lda sfspec._stored_p
pha
ldy sfspec._stored_y
ldx sfspec._stored_x
lda sfspec._stored_a
plp // restore p
}
.pseudocommand assert_bytes_equal bytes_count; actual; expected; pass_subroutine; fail_subroutine {
// TODO: remove pages and remainder branches
.var remainder = mod(bytes_count.getValue(), 256)
.var offset = bytes_count.getValue() - remainder
.var pages = offset / 256
ldy #pages
beq !end+
loopy:
ldx #0
loopx:
.label actual_hi = * + 2
lda actual.getValue(), X
.label expected_hi = * + 2
cmp expected.getValue(), X
bne pass_or_fail.fail
inx
bne loopx
inc actual_hi
inc expected_hi
dey
bne loopy
!end:
ldy #remainder
beq !end+
ldx #0
loop:
lda offset + actual.getValue(), X
cmp offset + expected.getValue(), X
bne pass_or_fail.fail
inx
cpx #remainder
bne loop
!end:
pass_or_fail: :_pass_or_fail pass_subroutine; fail_subroutine
}
.pseudocommand assert_pass pass_subroutine; fail_subroutine {
:_store_state()
jsr _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
:_restore_state()
}
.pseudocommand assert_fail pass_subroutine; fail_subroutine {
:_store_state()
jsr _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine)
:_restore_state()
}
.pseudocommand _pass_or_fail pass_subroutine; fail_subroutine {
pass:
jsr _given_or_default(pass_subroutine, _64SPEC.assertion_passed_subroutine)
jmp end
fail:
jsr _given_or_default(fail_subroutine, _64SPEC.assertion_failed_subroutine)
end:
}
.macro describe(subject) {
.if (_64SPEC.print_context_description) {
jmp end_text
text:
.if (_64SPEC.print_immediate_result || _64SPEC.print_example_description) {
.byte _CR
}
:_64spec_pet_text(subject)
.byte ' '
.byte 0
scoring:
.text " "
.byte _CR
.byte 0
end_text:
:_finalize_last_context()
:_finalize_last_example()
lda sfspec._description_data + 2
ora #%10000000
sta sfspec._description_data + 2
:_64spec_mov16 #text; sfspec._last_context
:_64spec_kernal_plot_get sfspec._last_context + 2; sfspec._last_context + 3
:_set_text_color #_64SPEC.text_color
:_print_string #text
:_print_string #scoring
}
}
.macro _finalize_last_context() {
.if (_64SPEC.print_context_description ) {
.if (_64SPEC.change_context_description_text_color) {
:_calculate_tests_result(sfspec._last_context + 4)
bit sfspec._last_context + 8
bvs pass
fail:
:_set_text_color #_64SPEC.failure_color
jmp end_color
pass:
:_set_text_color #_64SPEC.success_color
end_color:
}
.if (_64SPEC.change_context_description_text_color || _64SPEC.print_context_results) {
bit sfspec._description_data + 2
bvc end
:_64spec_kernal_plot_get sfspec._description_data; sfspec._description_data + 1
:_64spec_kernal_plot_set sfspec._last_context + 2; sfspec._last_context + 3
:_print_string sfspec._last_context
:_print_result_numbers(sfspec._last_context + 4)
:_64spec_kernal_plot_set sfspec._description_data; sfspec._description_data + 1
:_reset_tests_result(sfspec._last_context + 4)
end:
}
}
}
.macro it(description) {
.if (_64SPEC.print_example_description) {
jmp end_text
text:
.if (_64SPEC.print_immediate_result) {
.byte _CR
}
.byte ' '
:_64spec_pet_text(description)
.byte ' '
.byte 0
scoring:
.text " "
.byte _CR
.byte 0
end_text:
:_finalize_last_example()
lda sfspec._description_data + 2
ora #%01000000
sta sfspec._description_data + 2
:_64spec_mov16 #text; sfspec._last_example
:_64spec_kernal_plot_get sfspec._last_example + 2; sfspec._last_example + 3
:_set_text_color #_64SPEC.text_color
:_print_string #text
:_print_string #scoring
}
}
.macro _finalize_last_example() {
.if (_64SPEC.print_example_description) {
.if (_64SPEC.change_example_description_text_color) {
:_calculate_tests_result(sfspec._last_example + 4)
bit sfspec._last_example + 8
bvs pass
fail:
:_set_text_color #_64SPEC.failure_color
jmp end_color
pass:
:_set_text_color #_64SPEC.success_color
end_color:
}
.if (_64SPEC.change_example_description_text_color || _64SPEC.print_example_results) {
bit sfspec._description_data + 2
bvc end
:_64spec_kernal_plot_get sfspec._description_data; sfspec._description_data + 1
:_64spec_kernal_plot_set sfspec._last_example + 2; sfspec._last_example + 3
:_print_string sfspec._last_example
:_print_result_numbers(sfspec._last_example + 4)
:_64spec_kernal_plot_set sfspec._description_data; sfspec._description_data + 1
:_reset_tests_result(sfspec._last_example + 4)
end:
}
}
}
// helper functions
.function _given_or_default(given, default) {
.if (given.getType() == AT_NONE) {
.return CmdArgument(AT_ABSOLUTE, default)
} else {
.return given
}
}
.function _64spec_extract_byte_argument(arg, byte_id) {
.if (arg.getType()==AT_IMMEDIATE) {
.return CmdArgument(arg.getType(), _extract_byte(arg.getValue(), byte_id))
} else {
.return CmdArgument(arg.getType(), arg.getValue() + byte_id)
}
}
.function _extract_byte(value, byte_id) {
.var bits = _bytes_to_bits(byte_id)
.eval value = value >> bits
.return value & $ff
}
.function _bytes_to_bits(bytes) {
.return bytes * 8
}
.function _bits_to_bytes(bits) {
.return bits / 8
}
.pseudocommand _print_string string {
:_64spec_mov16 string; sfspec._print_string.string_address
jsr sfspec._print_string
}
.macro _print_string(string) {
ldy #0
loop:
.label string_address = * + 1
lda string, Y
beq end
jsr _CHROUT
iny
jmp loop
end:
}
.pseudocommand _print_char char {
lda char
jsr _CHROUT
}
.pseudocommand _print_int8 value {
ldx value
lda #0
jsr _PRINTWORD
}
.pseudocommand _print_int16 value {
ldx _64spec_extract_byte_argument(value, 0)
lda _64spec_extract_byte_argument(value, 1)
jsr _PRINTWORD
}
.pseudocommand _set_text_color color {
.if (_64SPEC.change_text_color) {
:_64spec_mov color; _TEXT_COLOR
}
}
.pseudocommand _64spec_mov source; destination {
:_64spec__mov _bits_to_bytes(8); source; destination
}
.pseudocommand _64spec_mov16 source; destination {
:_64spec__mov _bits_to_bytes(16); source; destination
}
.pseudocommand _64spec__mov bytes_count; source; destination {
.for (var i = 0; i < bytes_count.getValue(); i++) {
lda _64spec_extract_byte_argument(source, i)
sta _64spec_extract_byte_argument(destination, i)
}
}
.pseudocommand _64spec_inc16 arg {
:_64spec__inc _bits_to_bytes(16); arg
}
.pseudocommand _64spec__inc bytes;arg {
.for (var byte_id = 0;byte_id < bytes.getValue(); byte_id++) {
inc _64spec_extract_byte_argument(arg, byte_id)
bne end
}
end:
}
.macro _64spec_pet_text(string) {
.fill string.size(), _64spec_scr_to_pet(string.charAt(i))
}
.function _64spec_scr_to_pet(screencode) {
.var result = screencode
.if (screencode < 32) {
.return result + 64
}
.if (screencode < 64) {
.return result
}
.if (screencode < 95) {
.return result + 128
}
.if (screencode == 95) { // underscore
.return 164
}
.if (screencode < 128) {
.return result + 64
}
.if (screencode < 160) {
.return result - 128
}
.if (screencode < 224) {
.return result - 64
}
.return result
}
.macro _64spec_open_file_for_writing(string, logical_file_number) {
jmp end_filename
filename:
:_64spec_pet_text(string)
:_64spec_pet_text(",p,w")
end_filename:
:_64spec_kernal_setnam #[end_filename - filename]; #filename
:_64spec_kernal_setlfs #logical_file_number; #8; #2
:_64spec_kernal_open
}
.macro _64spec_close_file(logical_file_number) {
lda #logical_file_number
jsr _CLOSE
}
.macro _64spec_set_file_output(logical_file_number) {
ldx #logical_file_number
jsr _CHKOUT
}
.macro _64spec_set_screen_output() {
jsr _CLRCHN
}
.pseudocommand _64spec_kernal_setnam length; string_address {
lda length
ldx _64spec_extract_byte_argument(string_address, 0)
ldy _64spec_extract_byte_argument(string_address, 1)
jsr _SETNAM
}
.pseudocommand _64spec_kernal_setlfs logical_file_number; device_number; command {
lda logical_file_number
ldx device_number
ldy command
jsr _SETLFS
}
.pseudocommand _64spec_kernal_open {
jsr _OPEN
}
.pseudocommand _64spec_kernal_plot_get column; row {
sec
jsr _PLOT
.if (column.getType() != AT_NONE) {
stx row
}
.if (row.getType() != AT_NONE) {
sty column
}
}
.pseudocommand _64spec_kernal_plot_set column; row {
clc
ldx row
ldy column
jsr _PLOT
}