M .kdev4/jasm.kdev4 +7 -25
@@ 16,42 16,24 @@ Install Directory=/usr/local
Runtime=Host System
[Launch]
-Launch Configurations=Launch Configuration 0,Launch Configuration 1,Launch Configuration 2
+Launch Configurations=Launch Configuration 2,Launch Configuration 0
[Launch][Launch Configuration 0]
Configured Launch Modes=execute
Configured Launchers=nativeAppLauncher
-Name=jasm-6502
+Name=jasm
Type=Native Application
[Launch][Launch Configuration 0][Data]
-Arguments=test.jasm -v3 test.prg
-Dependencies=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00\x00\x03\x00\x00\x00\x08\x00j\x00a\x00s\x00m\x00\x00\x00\x12\x00j\x00a\x00s\x00m\x00-\x006\x005\x000\x002\x00\x00\x00\x12\x00j\x00a\x00s\x00m\x00-\x006\x005\x000\x002)
-Dependency Action=Build
+Arguments=-v3 test/test.jasm test.prg
+Dependencies=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x00)
+Dependency Action=Nothing
EnvironmentGroup=
Executable=file:///home/jonas/dev/c64/jasm
External Terminal=konsole --noclose --workdir %workdir -e %exe
-Project Target=jasm,jasm-6502,jasm-6502
+Project Target=jasm,jasm,jasm
Use External Terminal=false
-Working Directory=file:///home/jonas/dev/c64/jasm/jasm/test
-isExecutable=false
-
-[Launch][Launch Configuration 1]
-Configured Launch Modes=execute
-Configured Launchers=nativeAppLauncher
-Name=jasm-z80
-Type=Native Application
-
-[Launch][Launch Configuration 1][Data]
-Arguments=test.jasm -v3 test.prg
-Dependencies=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00\x00\x03\x00\x00\x00\x08\x00j\x00a\x00s\x00m\x00\x00\x00\x10\x00j\x00a\x00s\x00m\x00-\x00z\x008\x000\x00\x00\x00\x10\x00j\x00a\x00s\x00m\x00-\x00z\x008\x000)
-Dependency Action=Build
-EnvironmentGroup=
-Executable=file:///home/jonas/dev/c64/jasm
-External Terminal=konsole --noclose --workdir %workdir -e %exe
-Project Target=jasm,jasm-z80,jasm-z80
-Use External Terminal=false
-Working Directory=file:///home/jonas/dev/c64/jasm/jasm/test
+Working Directory=file:///home/jonas/dev/c64/jasm/jasm
isExecutable=false
[Launch][Launch Configuration 2]
M CMakeLists.txt +1 -2
@@ 17,5 17,4 @@ endif("${CMAKE_BUILD_TYPE}" STREQUAL "Re
add_subdirectory(core)
add_subdirectory(hasher)
-add_subdirectory(jasm-6502)
-add_subdirectory(jasm-z80)
+add_subdirectory(jasm)
M core/core.cbp +1 -0
@@ 102,6 102,7 @@
<Unit filename="core/collections/hash_map.h" />
<Unit filename="core/collections/null_hash_compare.h" />
<Unit filename="core/collections/static_array.h" />
+ <Unit filename="core/collections/string_hash_functor.h" />
<Unit filename="core/debug/timer.cpp" />
<Unit filename="core/debug/timer.h" />
<Unit filename="core/environment/log.cpp" />
M core/core.vcxproj +1 -0
@@ 210,6 210,7 @@
<ClInclude Include="core\collections\hash_map.h" />
<ClInclude Include="core\collections\null_hash_compare.h" />
<ClInclude Include="core\collections\static_array.h" />
+ <ClInclude Include="core\collections\string_hash_functor.h" />
<ClInclude Include="core\debug\timer.h" />
<ClInclude Include="core\environment\log.h" />
<ClInclude Include="core\exceptions\exception.h" />
M core/core.vcxproj.filters +3 -0
@@ 40,6 40,9 @@
<ClInclude Include="core\collections\null_hash_compare.h">
<Filter>collections</Filter>
</ClInclude>
+ <ClInclude Include="core\collections\string_hash_functor.h">
+ <Filter>collections</Filter>
+ </ClInclude>
<ClInclude Include="core\debug\timer.h">
<Filter>debug</Filter>
</ClInclude>
M core/core/collections/null_hash_compare.h +0 -2
@@ 1,7 1,5 @@
#pragma once
-#include <functional>
-
namespace core
{
A => core/core/collections/string_hash_functor.h +22 -0
@@ 0,0 1,22 @@
+#pragma once
+
+#include <core/strings/murmur_hash.h>
+
+namespace core
+{
+
+/// @addtogroup collections
+/// @{
+
+/// Compare class for hash maps with a prehashed key.
+struct StringHashFunctor
+{
+ size_t operator()(const std::string &key) const
+ {
+ return murmur_hash3_string_x64_64(key);
+ }
+};
+
+/// @}
+
+} // namespace core
M core/core/io/file_helpers.cpp +8 -0
@@ 1,5 1,6 @@
#include "pch.h"
+#include <algorithm>
#include <core/io/file_helpers.h>
#include <sstream>
@@ 53,4 54,11 @@ std::string file_extension(const std::st
return filename.substr(pos, std::string::npos);
}
+std::string to_front_slashes(const std::string &path)
+{
+ std::string front_slash_path(path);
+ std::replace(front_slash_path.begin(), front_slash_path.end(), '\\', '/');
+ return front_slash_path;
+}
+
} // namespace core
M core/core/io/file_helpers.h +5 -1
@@ 7,7 7,7 @@ namespace core
/// @{
/// Determines if there is an include directory that matches a file part.
-/// @ return True if a file part matches an include directory. @a path is updated in that case.
+/// @return True if a file part matches an include directory. @a result is updated in that case.
bool match_include_dir_and_file(const std::string &file, const std::vector<std::string> &include_dirs, std::string &result);
/// Check if a file exists.
@@ 20,6 20,10 @@ std::string base_name(const std::string
/// Returns the file extension including the punctual character.
std::string file_extension(const std::string &filename);
+/// Change all back-slashes to front-slashes.
+std::string to_front_slashes(const std::string &path);
+
+
/// @}
} // namespace core
M core/core/io/file_id_linux.h +7 -0
@@ 17,6 17,13 @@ struct FileId
{
return a.device == b.device && a.inode == b.inode;
}
+ friend bool operator<(const FileId &a, const FileId &b)
+ {
+ if (a.device < b.device) {
+ return true;
+ }
+ return a.inode < b.inode;
+ }
dev_t device;
ino_t inode;
};
M core/core/io/file_id_win.h +10 -0
@@ 17,6 17,16 @@ struct FileId
&& a.file_index_hi == b.file_index_hi
&& a.file_index_lo == b.file_index_lo;
}
+ friend bool operator<(const FileId &a, const FileId &b)
+ {
+ if (a.serial_number < b.serial_number) {
+ return true;
+ }
+ if (a.file_index_hi < b.file_index_hi) {
+ return true;
+ }
+ return a.file_index_lo < b.file_index_lo;
+ }
DWORD serial_number;
DWORD file_index_hi;
DWORD file_index_lo;
M hasher/main.cpp +2 -2
@@ 9,9 9,9 @@
#if defined(_MSC_VER)
int wmain(int argc, char16_t *argv[])
{
- std::wcout << std::hex << std::showbase;
+ std::cout << std::hex << std::showbase;
for (int i = 1; i < argc; ++i)
- std::wcout << argv[i] << L':' << core::murmur_hash3_string_x64_64(core::wide_to_utf8(argv[i])) << L'\n';
+ std::cout << argv[i] << ':' << core::murmur_hash3_string_x64_64(core::wide_to_utf8(argv[i])) << '\n';
return 0;
}
R jasm-6502/CMakeLists.txt => +0 -33
@@ 1,33 0,0 @@
-file(GLOB_RECURSE jasm_src
- "../jasm/*.h"
- "../jasm/*.cpp"
-)
-
-include_directories("../jasm")
-
-if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
- # using Clang
- add_compile_options(-Wall -Wnon-virtual-dtor -Wbind-to-temporary-copy -Wambiguous-member-template -Wextra-tokens -Weverything)
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
- # using GCC
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
- # using Visual Studio C++
-endif()
-
-if (${MINGW})
- add_compile_options(-static-libstdc++)
-endif()
-
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROCESSOR=0")
-
-set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
-
-add_executable(jasm-6502 ${jasm_src})
-
-set_property(TARGET jasm-6502 PROPERTY CXX_STANDARD 17)
-set_property(TARGET jasm-6502 PROPERTY CXX_STANDARD_REQUIRED ON)
-
-target_link_libraries(jasm-6502 core)
-
-install(TARGETS jasm-6502 CONFIGURATIONS Release RUNTIME DESTINATION /usr/bin)
R jasm-z80/CMakeLists.txt => +0 -33
@@ 1,33 0,0 @@
-file(GLOB_RECURSE jasm_src
- "../jasm/*.h"
- "../jasm/*.cpp"
-)
-
-include_directories("../jasm")
-
-if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
- # using Clang
- add_compile_options(-Wall -Wnon-virtual-dtor -Wbind-to-temporary-copy -Wambiguous-member-template -Wextra-tokens -Weverything)
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
- # using GCC
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
- # using Visual Studio C++
-endif()
-
-if (${MINGW})
- add_compile_options(-static-libstdc++)
-endif()
-
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROCESSOR=1")
-
-set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
-
-add_executable(jasm-z80 ${jasm_src})
-
-set_property(TARGET jasm-z80 PROPERTY CXX_STANDARD 17)
-set_property(TARGET jasm-z80 PROPERTY CXX_STANDARD_REQUIRED ON)
-
-target_link_libraries(jasm-z80 core)
-
-install(TARGETS jasm-z80 CONFIGURATIONS Release RUNTIME DESTINATION /usr/bin)
R jasm.sln => +0 -62
@@ 1,62 0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.23107.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jasm", "jasm\jasm.vcxproj", "{D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}"
- ProjectSection(ProjectDependencies) = postProject
- {082DD209-F99C-4F50-B360-7F0E75103418} = {082DD209-F99C-4F50-B360-7F0E75103418}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hasher", "hasher\hasher.vcxproj", "{1F130AF3-A85E-4BF5-9629-2787E58344E2}"
- ProjectSection(ProjectDependencies) = postProject
- {082DD209-F99C-4F50-B360-7F0E75103418} = {082DD209-F99C-4F50-B360-7F0E75103418}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core\core.vcxproj", "{082DD209-F99C-4F50-B360-7F0E75103418}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- debug-hasher|x64 = debug-hasher|x64
- debug-jasm-6502|x64 = debug-jasm-6502|x64
- debug-jasm-z80|x64 = debug-jasm-z80|x64
- release-hasher|x64 = release-hasher|x64
- release-jasm-6502|x64 = release-jasm-6502|x64
- release-jasm-z80|x64 = release-jasm-z80|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.debug-hasher|x64.ActiveCfg = debug-hasher|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.debug-jasm-6502|x64.ActiveCfg = debug-jasm-6502|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.debug-jasm-6502|x64.Build.0 = debug-jasm-6502|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.debug-jasm-z80|x64.ActiveCfg = debug-jasm-z80|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.debug-jasm-z80|x64.Build.0 = debug-jasm-z80|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.release-hasher|x64.ActiveCfg = release-hasher|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.release-jasm-6502|x64.ActiveCfg = release-jasm-6502|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.release-jasm-6502|x64.Build.0 = release-jasm-6502|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.release-jasm-z80|x64.ActiveCfg = release-jasm-z80|x64
- {D326DF3F-FBFA-4467-8918-B8B3D89BE0F3}.release-jasm-z80|x64.Build.0 = release-jasm-z80|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.debug-hasher|x64.ActiveCfg = debug-hasher|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.debug-hasher|x64.Build.0 = debug-hasher|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.debug-jasm-6502|x64.ActiveCfg = debug-jasm-6502|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.debug-jasm-z80|x64.ActiveCfg = debug-jasm-z80|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.release-hasher|x64.ActiveCfg = release-hasher|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.release-hasher|x64.Build.0 = release-hasher|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.release-jasm-6502|x64.ActiveCfg = release-jasm-6502|x64
- {1F130AF3-A85E-4BF5-9629-2787E58344E2}.release-jasm-z80|x64.ActiveCfg = release-jasm-z80|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-hasher|x64.ActiveCfg = debug-hasher|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-hasher|x64.Build.0 = debug-hasher|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-jasm-6502|x64.ActiveCfg = debug-jasm-6502|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-jasm-6502|x64.Build.0 = debug-jasm-6502|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-jasm-z80|x64.ActiveCfg = debug-jasm-z80|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.debug-jasm-z80|x64.Build.0 = debug-jasm-z80|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-hasher|x64.ActiveCfg = release-hasher|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-hasher|x64.Build.0 = release-hasher|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-jasm-6502|x64.ActiveCfg = release-jasm-6502|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-jasm-6502|x64.Build.0 = release-jasm-6502|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-jasm-z80|x64.ActiveCfg = release-jasm-z80|x64
- {082DD209-F99C-4F50-B360-7F0E75103418}.release-jasm-z80|x64.Build.0 = release-jasm-z80|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
A => jasm/CMakeLists.txt +33 -0
@@ 0,0 1,33 @@
+file(GLOB_RECURSE jasm_src
+ "./*.h"
+ "./*.cpp"
+)
+
+include_directories(".")
+
+if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ # using Clang
+ add_compile_options(-Wall -Wnon-virtual-dtor -Wbind-to-temporary-copy -Wambiguous-member-template -Wextra-tokens -Weverything)
+elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ # using GCC
+elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
+ # using Visual Studio C++
+endif()
+
+if (${MINGW})
+ add_compile_options(-static-libstdc++)
+endif()
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
+
+add_executable(jasm ${jasm_src})
+
+set_property(TARGET jasm PROPERTY CXX_STANDARD 17)
+set_property(TARGET jasm PROPERTY CXX_STANDARD_REQUIRED ON)
+
+target_link_libraries(jasm core)
+
+install(TARGETS jasm CONFIGURATIONS Release RUNTIME DESTINATION /usr/bin)
M jasm/assembling/assembler.cpp => jasm/assemble/assembler.cpp +25 -14
@@ 1,37 1,48 @@
#include "pch.h"
-#include <assembling/assembler.h>
-#include <assembling/assembler_impl/assembler_impl.h>
+#include <assemble/assembler.h>
+#include <assemble/assembler_impl/assembler_impl.h>
namespace jasm
{
-std::vector<Section> assemble(bool multiple_output_files, bool multi_bank_mode, bool pseudo_instructions
- , const std::vector<TokenChain> &syntax, StringRepository &strings
- , const HashArrayRepository &hash_arrays, const std::vector<std::string> &used_files
- , const std::vector<std::pair<std::string, bool>> &predefined_booleans
- , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
- , const std::vector<std::pair<std::string, std::string>> &predefined_strings
- , DataReader &data_reader, int32_t max_errors
- , const std::string &symbol_dump_file, const std::string &vice_dump_file, const std::string &gba_dump_file)
+std::vector<Section> assemble(
+ bool multiple_output_files,
+ bool multi_bank_mode,
+ bool pseudo_instructions,
+ ProcessorType default_processor,
+ const std::string &filename,
+ const std::vector<std::string> &include_dirs,
+ StringRepository &strings,
+ std::vector<std::string> &used_files,
+ const std::vector<std::pair<std::string, bool>> &predefined_booleans,
+ const std::vector<std::pair<std::string, int32_t>> &predefined_integers,
+ const std::vector<std::pair<std::string, std::string>> &predefined_strings,
+ int32_t max_errors,
+ const std::string &symbol_dump_file,
+ const std::string &vice_dump_file,
+ const std::string &gba_dump_file,
+ const std::string &output_hex_file
+)
{
std::vector<Section> output;
Assembler assembler(
- multiple_output_files
+ multiple_output_files
, multi_bank_mode
, pseudo_instructions
- , syntax
+ , default_processor
+ , filename
+ , include_dirs
, strings
- , hash_arrays
, used_files
, predefined_booleans
, predefined_integers
, predefined_strings
- , data_reader
, max_errors
, symbol_dump_file
, vice_dump_file
, gba_dump_file
+ , output_hex_file
, output
);
assembler.assemble();
M jasm/assembling/assembler.h => jasm/assemble/assembler.h +23 -13
@@ 1,29 1,39 @@
#pragma once
-#include <parsing/token_chain.h>
-#include <parsing/section.h>
+#include <core/collections/split_vector.h>
+#include <processor/processor.h>
+#include <syntax/section.h>
+#include <utility/token_chain.h>
#include <vector>
namespace jasm
{
-class DataReader;
class StringRepository;
-class HashArrayRepository;
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
/// Assemble the provided syntax chain.
/// @return A vector of sections that provides output data. No section is empty and there are only code sections.
-std::vector<Section> assemble(bool multiple_output_files, bool multi_bank_mode, bool pseudo_instructions
- , const std::vector<TokenChain> &syntax, StringRepository &strings
- , const HashArrayRepository &hash_arrays, const std::vector<std::string> &used_files
- , const std::vector<std::pair<std::string, bool>> &predefined_booleans
- , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
- , const std::vector<std::pair<std::string, std::string>> &predefined_strings
- , DataReader &data_reader, int32_t max_errors
- , const std::string &symbol_dump_file, const std::string &vice_dump_file, const std::string &gba_dump_file);
+std::vector<Section> assemble(
+ bool multiple_output_files
+ , bool multi_bank_mode
+ , bool pseudo_instructions
+ , ProcessorType default_processor
+ , const std::string &filename
+ , const std::vector<std::string> &include_dirs
+ , StringRepository &strings
+ , std::vector<std::string> &used_files
+ , const std::vector<std::pair<std::string, bool>> &predefined_booleans
+ , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
+ , const std::vector<std::pair<std::string, std::string>> &predefined_strings
+ , int32_t max_errors
+ , const std::string &symbol_dump_file
+ , const std::string &vice_dump_file
+ , const std::string &gba_dump_file
+ , const std::string &output_hex_file
+);
/// @}
M jasm/assembling/assembler_impl/assembler_impl.cpp => jasm/assemble/assembler_impl/assembler_impl.cpp +210 -23
@@ 1,46 1,69 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/type_description.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/type_description.h>
#include <core/debug/timer.h>
#include <core/environment/log.h>
#include <core/io/file_helpers.h>
+#include <core/io/file_id.h>
#include <core/io/file_writer.h>
+#include <core/math/sign.h>
#include <core/strings/utf8.h>
#include <exceptions/assembly_exception.h>
#include <exceptions/error_codes.h>
+#include <io/hex_source_writer.h>
#include <iomanip>
+#include <syntax/syntax_parse.h>
#include <sstream>
+#include <tokenize/tokenize.h>
namespace jasm {
using namespace core;
-Assembler::Assembler(bool multiple_output_files, bool multi_bank_mode, bool pseudo_instructions
- , const std::vector<TokenChain> &syntax, StringRepository &strings
- , const HashArrayRepository &hash_arrays, const std::vector<std::string> &used_files
- , const std::vector<std::pair<std::string, bool>> &predefined_booleans
- , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
- , const std::vector<std::pair<std::string, std::string>> &predefined_strings
- , DataReader &data_reader, int32_t max_errors
- , const std::string &symbol_dump_file, const std::string &vice_dump_file, const std::string &gba_dump_file
- , std::vector<Section> &output)
+Assembler::Assembler(
+ bool multiple_output_files
+ , bool multi_bank_mode
+ , bool pseudo_instructions
+ , ProcessorType default_processor
+ , const std::string &filename
+ , const std::vector<std::string> &include_dirs
+ , StringRepository &strings
+ , std::vector<std::string> &used_files
+ , const std::vector<std::pair<std::string, bool>> &predefined_booleans
+ , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
+ , const std::vector<std::pair<std::string, std::string>> &predefined_strings
+ , int32_t max_errors
+ , const std::string &symbol_dump_file
+ , const std::string &vice_dump_file
+ , const std::string &gba_dump_file
+ , const std::string &output_hex_file
+ , std::vector<Section> &output
+)
: _multiple_output_files(multiple_output_files)
, _multi_bank_mode(multi_bank_mode)
, _pseudo_instructions(pseudo_instructions)
- , _input(syntax)
+ , _default_processor(default_processor)
+ , _input_filename(filename)
+ , _include_dirs(include_dirs)
, _strings(strings)
- , _hash_arrays(hash_arrays)
, _used_files(used_files)
, _predefined_booleans(predefined_booleans)
, _predefined_integers(predefined_integers)
, _predefined_strings(predefined_strings)
- , _data_reader(data_reader)
, _max_errors(max_errors)
, _symbol_dump_file(symbol_dump_file)
, _vice_dump_file(vice_dump_file)
, _gba_dump_file(gba_dump_file)
+ , _output_hex_file(output_hex_file)
, _dump_symbols(!symbol_dump_file.empty() || !vice_dump_file.empty() || !gba_dump_file.empty())
+ , _dump_hex(!output_hex_file.empty())
+ , _hash_arrays(128)
+ , _processor(nullptr)
+ , _processor_type(ProcessorType::Unspecified)
+ , _catalogue(pseudo_instructions)
+ , _hex_source_writer(nullptr)
+ , _data_reader(include_dirs)
, _current_pass(token_chain_type_buffer_size)
, _previous_pass(token_chain_type_buffer_size)
, _oscillating_state(false)
@@ 79,6 102,7 @@ Assembler::Assembler(bool multiple_outpu
_section_mapping_stack.reserve(16);
_location_stack.reserve(8);
+ _processor_stack.reserve(4);
_sections.clear();
_sections.reserve(16);
@@ 90,6 114,13 @@ Assembler::Assembler(bool multiple_outpu
_symbol_names[0] = "";
}
+Assembler::~Assembler()
+{
+ if (_hex_source_writer != nullptr) {
+ delete _hex_source_writer;
+ }
+}
+
void Assembler::fill_type_integer_operators(TypeDescription &type)
{
type.operators[static_cast<uint32_t>(OperatorType::Plus)] = &Assembler::operator_integer_add;
@@ 554,12 585,16 @@ void Assembler::run_assembly_pass(bool g
assert(_call_depth == 0);
assert(_data_generation_depth == 0);
- _input_reader = TokenReader(_input[0]);
_current_pass.type_reader = TokenReader(_current_pass.types);
_previous_pass.type_reader = TokenReader(_previous_pass.types);
_sections.clear();
_section = nullptr;
+ _processor_type = _default_processor;
+ _processor = _catalogue.processor(_processor_type);
+ _processor_stack.clear();
+ _processor_stack.push_back(_processor_type);
+
// reset scopes since we may place things directly inside the global scope
// and these things will not be reset otherwise
_symbol_environment.reset();
@@ 579,13 614,8 @@ void Assembler::run_assembly_pass(bool g
// reset program counter
set_integer(_program_counter, 0);
- const SyntaxToken *t = consume_next_token();
- bool early_return = false;
- t = parse_inner_scope(generate, t, early_return);
- assert(!early_return); // this can't be inside a macro so this should not be possible
-
- assert(t->type == SyntaxTokenType::End);
-
+ parse_file(generate, _input_filename, nullptr);
+
if (generate) {
if (_dump_symbols) {
if (!_symbol_dump_file.empty()) {
@@ 611,6 641,152 @@ void Assembler::run_assembly_pass(bool g
}
+void Assembler::parse_file(bool generate, const std::string &filename, const SourceLocation *include_location)
+{
+ size_t file_index = syntax_analyze(filename, include_location);
+
+ const SyntaxResult &syntax_result = _syntax_results[file_index];
+
+ if (syntax_result.error_code != AssemblyErrorCodes::Ok) {
+ if (generate) {
+ report_error(syntax_result.error_location, syntax_result.error_code, syntax_result.error_message);
+ }
+ return;
+ }
+
+ if (std::find(_include_id_history.begin(), _include_id_history.end(), syntax_result.file_id) != _include_id_history.end()) {
+ std::stringstream ss;
+ ss << "Include file recursion. '" << _used_files[file_index] << "' is included twice from:";
+ for (auto it = _include_file_history.rbegin(); it != _include_file_history.rend(); ++it)
+ ss << "\n " << to_front_slashes(*it);
+ throw AssemblyException(_used_files[file_index], 1, 1, AssemblyErrorCodes::RecursiveIncludes, ss.str());
+ }
+
+ // save read chain and pointer to move back after the file
+ TokenChainScope tcs(_input_reader);
+
+ _include_id_history.push_back(syntax_result.file_id);
+ _include_file_history.push_back(filename);
+
+ size_t processor_depth = _processor_stack.size();
+
+ _input_reader = TokenReader(*_input[syntax_result.token_chain_index]);
+ const SyntaxToken *t = consume_next_token();
+ bool early_return = false;
+ t = parse_inner_scope(generate, t, early_return);
+ assert(!early_return); // this can't be inside a macro so this should not be possible
+ assert(t->type == SyntaxTokenType::End);
+
+ assert(_processor_stack.size() >= processor_depth);
+ _processor_stack.resize(processor_depth);
+ _processor_type = _processor_stack.back();
+ _processor = _catalogue.processor(_processor_type);
+
+ _include_file_history.pop_back();
+ _include_id_history.pop_back();
+}
+
+size_t Assembler::syntax_analyze(const std::string &filename, const SourceLocation *include_location)
+{
+ auto file_index_it = _file_to_index.find(filename);
+ if (file_index_it != _file_to_index.end()) {
+ // we already processed this file
+ return file_index_it->second;
+ }
+
+ // check if the same file exists but as another name
+ core::FileId fid;
+ std::string file_path = filename;
+ match_include_dir_and_file(filename, _include_dirs, file_path);
+ if (!core::file_id(file_path, fid)) {
+ size_t file_index = _used_files.size();
+ // make sure that the file has front slashes to get the output from linux and pc unit tests match
+ _used_files.emplace_back(core::to_front_slashes(filename));
+ _input.emplace_back();
+ if (_dump_hex) {
+ _file_row_locations.emplace_back();
+ _file_contents.emplace_back();
+ }
+
+ SyntaxResult result;
+ result.file_id = fid;
+ result.error_code = AssemblyErrorCodes::CantFindIncludeFile;
+ result.token_chain_index = 0;
+
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << "'";
+ result.error_message = ss.str();
+
+ if (include_location == nullptr) {
+ // this is the main file
+ result.error_location.file_index = static_cast<uint32_t>(file_index);
+ result.error_location.column = 1;
+ result.error_location.row = 1;
+ } else {
+ // we came from an include statement
+ result.error_location.file_index = include_location->file_index;
+ result.error_location.column = include_location->column;
+ result.error_location.row = include_location->row;
+ }
+ _syntax_results.emplace_back(std::move(result));
+ return file_index;
+ }
+ // file exists, now check for previous file with different name
+ auto syntax_result_it = std::find_if(std::begin(_syntax_results), std::end(_syntax_results), [&fid](const SyntaxResult &r){ return r.file_id == fid; });
+ if (syntax_result_it != std::end(_syntax_results)) {
+ // the file has already been parsed!
+ return core::unsign_cast(syntax_result_it - std::begin(_syntax_results));
+ }
+
+ size_t file_index = _used_files.size();
+
+ // record this new file
+ _used_files.emplace_back(core::to_front_slashes(filename));
+ _syntax_results.emplace_back();
+ if (_dump_hex) {
+ _file_row_locations.emplace_back();
+ _file_contents.emplace_back();
+ }
+ SyntaxResult &syntax_result = _syntax_results.back();
+ syntax_result.file_id = fid;
+ syntax_result.error_code = AssemblyErrorCodes::Ok;
+ syntax_result.token_chain_index = _input.size();
+
+ // tokenize
+ std::vector<size_t> *row_locations_ptr = nullptr;
+ std::wstring *contents_ptr = nullptr;
+ if (_dump_hex) {
+ row_locations_ptr = &_file_row_locations.back();
+ contents_ptr = &_file_contents.back();
+ }
+
+ // TODO: Catch exceptions here to allow parse error in files that doesn't contribute to final output.
+ // However, to do that the tokenizer and syntax parser must return more error details.
+ TokenChain tokens = tokenize(
+ static_cast<uint32_t>(file_index),
+ core::to_front_slashes(filename),
+ file_path,
+ _catalogue,
+ _processor_type,
+ _strings,
+ row_locations_ptr,
+ contents_ptr
+ );
+
+ // syntax parse
+ parse_syntax(
+ tokens,
+ _input,
+ _catalogue,
+ _processor_type,
+ _strings,
+ _hash_arrays,
+ _used_files
+ );
+
+ return file_index;
+}
+
void Assembler::collect_section_sizes(const std::vector<Section> §ions)
{
for(const auto §ion : sections) {
@@ 1175,6 1351,11 @@ void Assembler::assemble()
debug() << ss.str();
}
+ if (_dump_hex) {
+ _hex_source_writer = new HexSourceWriter(_file_row_locations, _file_contents, _used_files);
+ _hex_source_writer->open_output(_output_hex_file);
+ }
+
{
TimerScope timer("Generate pass");
prepare_next_assembly_pass();
@@ 1183,7 1364,13 @@ void Assembler::assemble()
if (_num_errors != 0 || _oscillating_state)
throw AssemblyException("Assembly ended with errors.");
}
-
+
+ if (_hex_source_writer != nullptr) {
+ _hex_source_writer->close();
+ delete _hex_source_writer;
+ _hex_source_writer = nullptr;
+ }
+
std::sort(_sections.begin(), _sections.end());
print_sections();
M jasm/assembling/assembler_impl/assembler_impl.h => jasm/assemble/assembler_impl/assembler_impl.h +103 -27
@@ 1,29 1,41 @@
#pragma once
-#include <assembling/function_pointer.h>
-#include <assembling/method_pointer.h>
-#include <assembling/symbol_environment.h>
-#include <assembling/type_description.h>
-#include <assembling/value.h>
+#include <assemble/function_pointer.h>
+#include <assemble/method_pointer.h>
+#include <assemble/symbol_environment.h>
+#include <assemble/type_description.h>
+#include <assemble/value.h>
#include <core/math/algorithm.h>
#include <core/collections/hash_map.h>
+#include <core/collections/split_vector.h>
+#include <core/collections/string_hash_functor.h>
+#include <core/io/file_id.h>
#include <exceptions/assembly_exception.h>
#include <exceptions/error_codes.h>
#include <functional>
-#include <parsing/hasharray_repository.h>
-#include <parsing/syntax_parser.h>
+#include <io/data_reader.h>
#include <set>
#include <strings/string_conversions.h>
#include <strings/string_locale.h>
#include <strings/string_repository.h>
#include <sstream>
+#include <syntax/syntax_parser.h>
+#include <utility/hasharray_repository.h>
namespace jasm
{
+ namespace mos6502
+ {
+ class Processor6502;
+ }
+ namespace z80
+ {
+ class ProcessorZ80;
+ }
-class DataReader;
+class HexSourceWriter;
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
constexpr uint32_t token_chain_type_buffer_size = 65536;
@@ 45,16 57,30 @@ constexpr uint32_t max_call_depth = 100;
/// to form the final hash.
class Assembler
{
+ friend mos6502::Processor6502;
+ friend z80::ProcessorZ80;
+
public:
- Assembler(bool multiple_output_files, bool multi_bank_mode, bool pseudo_instructions
- , const std::vector<TokenChain> &syntax, StringRepository &strings
- , const HashArrayRepository &hash_arrays, const std::vector<std::string> &used_files
- , const std::vector<std::pair<std::string, bool>> &predefined_booleans
- , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
- , const std::vector<std::pair<std::string, std::string>> &predefined_strings
- , DataReader &data_reader, int32_t max_errors
- , const std::string &symbol_dump_file, const std::string &vice_dump_file, const std::string &gba_dump_file
- , std::vector<Section> &output);
+ Assembler(
+ bool multiple_output_files
+ , bool multi_bank_mode
+ , bool pseudo_instructions
+ , ProcessorType default_processor
+ , const std::string &filename
+ , const std::vector<std::string> &include_dirs
+ , StringRepository &strings
+ , std::vector<std::string> &used_files
+ , const std::vector<std::pair<std::string, bool>> &predefined_booleans
+ , const std::vector<std::pair<std::string, int32_t>> &predefined_integers
+ , const std::vector<std::pair<std::string, std::string>> &predefined_strings
+ , int32_t max_errors
+ , const std::string &symbol_dump_file
+ , const std::string &vice_dump_file
+ , const std::string &gba_dump_file
+ , const std::string &output_hex_file
+ , std::vector<Section> &output
+ );
+ ~Assembler();
Assembler &operator=(const Assembler &other) = delete;
@@ 805,6 831,11 @@ private:
/// Translate section name according to current section mappings.
uint64_t translate_section(uint64_t section_hash) const;
+ bool in_code_section() const
+ {
+ return _section != nullptr && _section->section_type == SectionType::Code;
+ }
+
/// Add a combined namespace to be used when looking up global symbols.
/// This will exist until the end of the current scope.
void add_using_namespace(uint64_t combined_hash);
@@ 914,11 945,6 @@ private:
const SyntaxToken *parse_section_map(bool generate, const SyntaxToken *t);
const SyntaxToken *parse_declare(bool generate, const SyntaxToken *t);
const SyntaxToken *parse_declaration(bool generate, const SyntaxToken *t, bool export_enabled);
- #if SUPPORTS(M6502)
- void generate_instruction_data_label(bool generate, bool export_enabled, const InstructionToken &token, int address, int offset, uint8_t size);
- #endif
- void generate_subroutine_instruction(bool generate, int32_t address, const SourceLocation &source_location);
- const SyntaxToken *parse_instruction(bool generate, const SyntaxToken *t, bool export_enabled);
const SyntaxToken *parse_reserve(bool generate, const SyntaxToken *t, bool export_enabled);
const SyntaxToken *parse_statement(bool generate, const SyntaxToken *t, bool &early_return);
const SyntaxToken *parse_statement_after_export(bool generate, const SyntaxToken *t, const SourceLocation &location);
@@ 927,6 953,7 @@ private:
const SyntaxToken *parse_namespace(bool generate, const SyntaxToken *t);
const SyntaxToken *parse_module(bool generate, const SyntaxToken *t);
const SyntaxToken *parse_export(bool generate, const SyntaxToken *t);
+ const SyntaxToken *parse_processor(const SyntaxToken *t);
const SyntaxToken *parse_define(bool generate, const SyntaxToken *t, bool export_enabled);
const SyntaxToken *parse_macro(bool generate, const SyntaxToken *t, bool export_enabled);
const SyntaxToken *parse_return(bool generate, const SyntaxToken *t);
@@ 938,6 965,7 @@ private:
const SyntaxToken *parse_using(bool generate, const SyntaxToken *t);
const SyntaxToken *parse_subroutine(bool generate, const SyntaxToken *t, bool export_enabled);
const SyntaxToken *parse_incbin(bool generate, const SyntaxToken *t);
+ const SyntaxToken *parse_include(bool generate, const SyntaxToken *t);
/// Get the variable name for a global or local variable.
inline std::string variable_name(uint64_t symbol_hash, bool global)
@@ 992,6 1020,13 @@ private:
bool progress_was_made();
+ /// Tokenize, syntax analyze and parse a file.
+ void parse_file(bool generate, const std::string &filename, const SourceLocation *include_location);
+ /// Attempt to parse or just return the file index if an attempt has already been made.
+ /// @param include_location Pointer to source location where include was made, or nullptr if this is the main file.
+ /// @return Index into _used_files and _syntax_results.
+ size_t syntax_analyze(const std::string &filename, const SourceLocation *include_location);
+
/// Dump all symbol information to a file.
void dump_symbols(const std::string &filename);
/// Dump all symbol information to a VICE compatible file.
@@ 1040,23 1075,64 @@ private:
bool _multiple_output_files; ///< When true, write one file per section. Otherwise merge them together.
bool _multi_bank_mode; ///< When true, the assembler will truncate addresses in instructions.
bool _pseudo_instructions; ///< When true, some extra instructions or addressing modes can be added to simplify programming.
- const std::vector<TokenChain> &_input; ///< The syntax token stream to run assemble passes on.
+ ProcessorType _default_processor; ///< The default processor to begin assembling with.
+ const std::string _input_filename; ///< The file to start assembling in.
+ const std::vector<std::string> &_include_dirs;
StringRepository &_strings; ///< The lookup table from uint64_t to strings.
- const HashArrayRepository &_hash_arrays; ///< The lookup table from uint64_t handles to uint64_t arrays.
- const std::vector<std::string> &_used_files; ///< Filenames of assembler files to be able to print them.
+ std::vector<std::string> &_used_files; ///< Filenames of assembler files to be able to print them.
const std::vector<std::pair<std::string, bool>> &_predefined_booleans;
const std::vector<std::pair<std::string, int32_t>> &_predefined_integers;
const std::vector<std::pair<std::string, std::string>> &_predefined_strings;
- DataReader &_data_reader; ///< This handles reading binary files.
int32_t _max_errors; ///< Max number of errors before aborting assembly generation pass.
const std::string _symbol_dump_file; ///< The filename of the symbol dump.
const std::string _vice_dump_file; ///< The filename of the vice symbol dump.
const std::string _gba_dump_file; ///< The filename of the No$GBA style symbol dump.
+ const std::string _output_hex_file; ///< The filename of the hex output dump.
bool _dump_symbols; ///< True if symbol dump is enabled.
+ bool _dump_hex; ///< True if hex dump is enabled.
+ // file handling
+
+ /// Cache with map from path to index into _used_files and _syntax_results.
+ core::HashMap<std::string, size_t, core::StringHashFunctor> _file_to_index;
+
+ struct SyntaxResult
+ {
+ /// ID of file to be able to find same files with different names.
+ core::FileId file_id;
+ /// Error code, or 0 if syntax analysis was successful.
+ AssemblyErrorCodes error_code;
+ /// Empty if no error occurred and the message in case there was a problem with loading, tokenizing or syntax analysis.
+ std::string error_message;
+ /// Location of error, if any.
+ SourceLocation error_location;
+ /// Token chain where the syntax output is stored.
+ size_t token_chain_index;
+ };
+ // List of syntax results, in the same order as _used_files.
+ std::vector<SyntaxResult> _syntax_results;
+
+ // tokenizer
+ std::vector<core::FileId> _include_id_history; ///< An array of include file identifiers used to determine file recursion.
+ std::vector<std::string> _include_file_history; ///< An array of include files in the same order as @a _include_id_history to be able to print the history in error messages.
+ core::SplitVector<std::vector<size_t>> _file_row_locations; ///< Character index of each row in each file, or empty if hex output is disabled.
+ /// All source file contents, or empty if hex output is disabled.
+ std::vector<std::wstring> _file_contents;
+
+ // syntax parser output
+ HashArrayRepository _hash_arrays; // repository to replace hash array references with a fixed size handle. The lookup table from uint64_t handles to uint64_t arrays.
+
+ const Processor *_processor; ///< Currently used processor.
+ ProcessorType _processor_type; ///< Currently used processor type.
+ std::vector<ProcessorType> _processor_stack; ///< Stack of processors where the top one is the current. This will never be empty.
+ ProcessorCatalogue _catalogue; ///< Keeps processor implementations.
StringLocale _string_locale; ///< Converts locale names to names to be used in std::locale.
StringConversions _string_conversions; ///< Converts strings to platform specific formats.
TokenReader _input_reader; ///< Reads the syntax input tokens.
+ HexSourceWriter *_hex_source_writer; ///< Writes hex data combined with source code.
+
+ std::vector<std::unique_ptr<TokenChain>> _input; ///< The syntax token stream to run assemble passes on.
+ DataReader _data_reader; ///< This handles reading binary files.
Pass _current_pass;
Pass _previous_pass;
M jasm/assembling/assembler_impl/expressions_impl.cpp => jasm/assemble/assembler_impl/expressions_impl.cpp +3 -3
@@ 1,8 1,8 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/type_description.h>
-#include <parsing/operators.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/type_description.h>
+#include <tokenize/operators.h>
namespace jasm {
M jasm/assembling/assembler_impl/functions_impl.cpp => jasm/assemble/assembler_impl/functions_impl.cpp +2 -2
@@ 1,7 1,7 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/functions.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/functions.h>
#include <cmath>
#include <core/environment/log.h>
#include <core/math/sign.h>
M jasm/assembling/assembler_impl/methods_impl.cpp => jasm/assemble/assembler_impl/methods_impl.cpp +2 -2
@@ 1,7 1,7 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/methods.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/methods.h>
#include <core/math/sign.h>
#include <core/strings/utf8.h>
M jasm/assembling/assembler_impl/operators_impl.cpp => jasm/assemble/assembler_impl/operators_impl.cpp +3 -3
@@ 1,6 1,6 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
+#include <assemble/assembler_impl/assembler_impl.h>
#include <core/math/sign.h>
#include <core/ownership/destruct_call.h>
#include <core/strings/utf8.h>
@@ 1044,7 1044,7 @@ void Assembler::operator_subroutine_call
return;
}
- generate_subroutine_instruction(generate, dereference_integer(arg1), components[operator_index].source_location);
+ _processor->generate_subroutine_instruction(*this, generate, dereference_integer(arg1), components[operator_index].source_location);
}
void Assembler::operator_function_call(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, uint32_t next_index)
@@ 1393,7 1393,7 @@ void Assembler::operator_call_macro(bool
// get first token in macro
const Value ¯o_value = arg1;
- TokenReader macro_reader(_input[macro_value.macro_chain_index]);
+ TokenReader macro_reader(*_input[macro_value.macro_chain_index]);
const MacroDefinitionToken *macro_def = macro_reader.next_type<MacroDefinitionToken>();
assert(macro_def->type == SyntaxTokenType::MacroDef);
M jasm/assembling/assembler_impl/symbols_impl.cpp => jasm/assemble/assembler_impl/symbols_impl.cpp +1 -1
@@ 1,6 1,6 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
+#include <assemble/assembler_impl/assembler_impl.h>
#include <core/math/sign.h>
#include <core/strings/utf8.h>
#include <cstring>
M jasm/assembling/assembler_impl/syntax_impl.cpp => jasm/assemble/assembler_impl/syntax_impl.cpp +126 -553
@@ 1,13 1,13 @@
#include "pch.h"
#include <algorithm>
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/scope_counter.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/scope_counter.h>
#include <core/environment/log.h>
#include <core/math/algorithm.h>
#include <core/math/sign.h>
#include <core/strings/utf8.h>
-#include <io/data_reader.h>
+#include <io/hex_source_writer.h>
#include <limits>
namespace jasm {
@@ 475,539 475,6 @@ const SyntaxToken *Assembler::parse_decl
return t;
}
-#if SUPPORTS(M6502)
-
-void Assembler::generate_subroutine_instruction(bool generate, int32_t address, const SourceLocation &source_location)
-{
- // instructions are only allowed within code sections.
- bool instructions_allowed = _section != nullptr && _section->section_type == SectionType::Code;
- if (UNLIKELY(!instructions_allowed)) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Instructions must be in a code section.";
- report_fatal_error(source_location, AssemblyErrorCodes::CodeMustBeInCodeSection, ss.str());
- }
-
- // recursive data generation may not be safe
- if (_data_generation_depth != 0) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Recursive data generation isn't allowed.";
- report_fatal_error(source_location, AssemblyErrorCodes::RecursiveDataGenerationNotAllowed, ss.str());
- }
-
- ScopeCounter<uint32_t> sc(_data_generation_depth);
-
- if (generate && address < 0) {
- std::stringstream ss;
- ss << "Addressing mode needs a positive argument. Argument value was evaluated to " << address << ".";
- report_error(source_location, AssemblyErrorCodes::AddressingModeRequiresPositiveArgument, ss.str());
- }
-
- if (_multi_bank_mode) {
- // in this mode, addresses gets truncated to support memory banks
- address &= 0xffff;
- }
-
- if (generate) {
- if (address > 65535) {
- std::stringstream ss;
- ss << "Addressing mode needs a word size argument. Argument was evaluated to " << address << ".";
- report_error(source_location, AssemblyErrorCodes::AddressingModeRequiresWordSizeArgument, ss.str());
- }
- Section::Contents ending_instruction = Section::Contents::ContinueExecutionInstruction;
- auto &data = _section->generated_data(ending_instruction);
- data.push_back(opcode(InstructionType::Jsr, AddressingModeType::AbsoluteAddr));
- data.push_back(static_cast<uint8_t>(address));
- data.push_back(static_cast<uint8_t>(address >> 8));
- }
- _program_counter.integer_value += 3;
-}
-
-void Assembler::generate_instruction_data_label(bool generate, bool export_enabled, const InstructionToken &token, int address, int offset, uint8_t size)
-{
- // exporting local variables is not allowed
- if (generate && export_enabled && !token.global_data_label) {
- std::stringstream ss;
- ss << variable_name(token.data_label_symbol_hash, token.global_data_label) << " cannot be exported since it is local.";
- report_error(token.address_label_location, AssemblyErrorCodes::ExportingLocalIsNotAllowed, ss.str());
- }
-
- if (create_label(generate, token.data_label_symbol_hash, token.global_data_label, StorageType::Constant, token.address_label_location)) {
- Value &new_label = _current_pass.values.back();
- if (size == 1) {
- set_byte_offset(new_label, address, offset);
- } else if (size == 2) {
- set_word_offset(new_label, address, offset);
- } else {
- assert(false);
- }
- new_label.set_contains_address(true);
- if (export_enabled) {
- new_label.set_is_public(true);
- }
- }
-}
-
-const SyntaxToken *Assembler::parse_instruction(bool generate, const SyntaxToken *t, bool export_enabled)
-{
- assert(t->type == SyntaxTokenType::Instruction);
- const InstructionToken &instruction_token = *static_cast<const InstructionToken *>(t);
-
- // instructions are only allowed within code sections.
- bool instructions_allowed = _section != nullptr && _section->section_type == SectionType::Code;
- if (UNLIKELY(!instructions_allowed)) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Instructions must be in a code section.";
- report_fatal_error(instruction_token.source_location, AssemblyErrorCodes::CodeMustBeInCodeSection, ss.str());
- }
-
- // recursive data generation may not be safe
- if (_data_generation_depth != 0) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Recursive data generation isn't allowed.";
- report_fatal_error(instruction_token.source_location, AssemblyErrorCodes::RecursiveDataGenerationNotAllowed, ss.str());
- }
- ScopeCounter<uint32_t> sc(_data_generation_depth);
-
- InstructionType instruction = instruction_token.instruction;
- uint16_t addr_mode = instruction_token.addressing_modes;
- Section::Contents ending_instruction = is_ending_instruction(instruction) ? Section::Contents::EndExecutionInstruction : Section::Contents::ContinueExecutionInstruction;
-
- t = consume_next_token(); // instruction
-
- // in the generation pass, the program counter is guaranteed to be an integer value
- // so there is no need to verify this
-
- if (addr_mode == AddressingModeMask::Imp) {
- if (generate) {
- _section->generated_data(ending_instruction).push_back(opcode(instruction, AddressingModeType::Implied));
- }
- ++_program_counter.integer_value;
- return t;
- }
-
- // in all other instructions, we have to parse the expression for the argument
- const ExpressionToken *expr = static_cast<const ExpressionToken *>(t);
- const Value argument = evaluate_expression(generate, t);
- t = consume_next_token();
-
- // The argument is guaranteed to be a valid type in the generation pass.
- // In an assembly pass this can be Unknown.
- int32_t argument_value = 0;
-
- // in case of an assembly pass, unknown will be converted to 0, which is ok for all addressing modes except relative
- if (is_integer(argument))
- argument_value = dereference_integer(argument);
- else if (!is_unknown(argument)) {
- if (generate) {
- std::stringstream ss;
- ss << "Addressing mode needs an integer value. Argument type was " << to_string(type_of_value(argument)) << ".";
- report_error(expr->source_location, AssemblyErrorCodes::AddressingModeRequiresIntegerArgument, ss.str());
- }
- }
-
- if (addr_mode == AddressingModeMask::Imm) {
- if (generate) {
- // handle the case where the value doesn't fit in a byte
- if (argument_value < -128 || argument_value > 255) {
- std::stringstream ss;
- ss << "Addressing mode needs a byte size argument. Argument was evaluated to " << argument_value << ".";
- report_error(expr->source_location, AssemblyErrorCodes::AddressingModeRequiresByteSizeArgument, ss.str());
- }
- auto &data = _section->generated_data(ending_instruction);
- data.push_back(opcode(instruction, AddressingModeType::Immediate));
- data.push_back(static_cast<uint8_t>(argument_value));
- }
- if (UNLIKELY(instruction_token.has_instruction_data_label)) {
- generate_instruction_data_label(generate, export_enabled, instruction_token, _program_counter.integer_value + 1, 0, 1);
- }
-
- _program_counter.integer_value += 2;
- return t;
- }
-
- if (generate && argument_value < 0) {
- std::stringstream ss;
- ss << "Addressing mode needs a positive argument. Argument value was evaluated to " << argument_value << ".";
- report_error(expr->source_location, AssemblyErrorCodes::AddressingModeRequiresPositiveArgument, ss.str());
- }
-
- if (_multi_bank_mode && addr_mode != AddressingModeMask::Rel) {
- // in this mode, addresses gets truncated to support memory banks
- argument_value &= 0xffff;
- }
-
- if (addr_mode == (AddressingModeMask::Zp | AddressingModeMask::Abs)
- || addr_mode == (AddressingModeMask::Zpx | AddressingModeMask::AbsX)
- || addr_mode == (AddressingModeMask::Zpy | AddressingModeMask::AbsY))
- {
- // mask off the zero page or absolute addressing mode regardless of modes
- addr_mode = argument_value > 255 ? select_word_mode(addr_mode) : select_byte_mode(addr_mode);
- }
-
- if (generate) {
- _section->generated_data(ending_instruction).push_back(opcode(instruction, mask_to_addressing_mode(addr_mode)));
- }
- ++_program_counter.integer_value;
-
- if (addr_mode == AddressingModeMask::Zp
- || addr_mode == AddressingModeMask::Zpx
- || addr_mode == AddressingModeMask::Zpy
- || addr_mode == AddressingModeMask::IndX
- || addr_mode == AddressingModeMask::IndY)
- {
- if (generate) {
- if (argument_value > 255) {
- std::stringstream ss;
- ss << "Addressing mode needs a byte size argument. Argument was evaluated to " << argument_value << ".";
- report_error(expr->source_location, AssemblyErrorCodes::AddressingModeRequiresByteSizeArgument, ss.str());
- }
- _section->generated_data(ending_instruction).push_back(static_cast<uint8_t>(argument_value));
- }
- if (UNLIKELY(instruction_token.has_instruction_data_label)) {
- generate_instruction_data_label(generate, export_enabled, instruction_token, _program_counter.integer_value, 0, 1);
- }
- ++_program_counter.integer_value;
- return t;
- }
-
- if (addr_mode == AddressingModeMask::Abs
- || addr_mode == AddressingModeMask::AbsX
- || addr_mode == AddressingModeMask::AbsY
- || addr_mode == AddressingModeMask::Ind)
- {
- if (generate) {
- if (argument_value > 65535) {
- std::stringstream ss;
- ss << "Addressing mode needs a word size argument. Argument was evaluated to " << argument_value << ".";
- report_error(expr->source_location, AssemblyErrorCodes::AddressingModeRequiresWordSizeArgument, ss.str());
- }
- auto &data = _section->generated_data(ending_instruction);
- data.push_back(static_cast<uint8_t>(argument_value));
- data.push_back(static_cast<uint8_t>(argument_value >> 8));
- }
- if (UNLIKELY(instruction_token.has_instruction_data_label)) {
- generate_instruction_data_label(generate, export_enabled, instruction_token, _program_counter.integer_value, 0, 2);
- }
- _program_counter.integer_value += 2;
- return t;
- }
-
- // only relative address left
- assert(addr_mode == AddressingModeMask::Rel);
-
- if (generate) {
- int32_t reference_addr = _program_counter.integer_value + 1; // program counter has already been increased once
- int32_t relative_addr = argument_value - reference_addr;
- if (relative_addr < -128 || relative_addr > 127) {
- std::stringstream ss;
- ss << "Relative address out of range. Offset is " << relative_addr << " and needs to be in a [-128..127] range.";
- report_error(expr->source_location, AssemblyErrorCodes::RelativeAddressOutOfRange, ss.str());
- }
- _section->generated_data(ending_instruction).push_back(static_cast<uint8_t>(relative_addr));
- }
- if (UNLIKELY(instruction_token.has_instruction_data_label)) {
- generate_instruction_data_label(generate, export_enabled, instruction_token, _program_counter.integer_value, 0, 1);
- }
- ++_program_counter.integer_value;
- return t;
-}
-
-#elif SUPPORTS(Z80)
-
-void Assembler::generate_subroutine_instruction(bool generate, int32_t address, const SourceLocation &source_location)
-{
- // instructions are only allowed within code sections.
- bool instructions_allowed = _section != nullptr && _section->section_type == SectionType::Code;
- if (UNLIKELY(!instructions_allowed)) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Instructions must be in a code section.";
- report_fatal_error(source_location, AssemblyErrorCodes::CodeMustBeInCodeSection, ss.str());
- }
-
- // recursive data generation may not be safe
- if (_data_generation_depth != 0) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Recursive data generation isn't allowed.";
- report_fatal_error(source_location, AssemblyErrorCodes::RecursiveDataGenerationNotAllowed, ss.str());
- }
-
- ScopeCounter<uint32_t> sc(_data_generation_depth);
-
- const InstructionOpCode &opcode_data = opcode(InstructionType::Call, 0);
- uint8_t mutable_opcode_data[4];
- mutable_opcode_data[0] = opcode_data.op[0];
- mutable_opcode_data[1] = opcode_data.op[1];
- mutable_opcode_data[2] = opcode_data.op[2];
- mutable_opcode_data[3] = opcode_data.op[3];
-
- if (generate) {
- if (UNLIKELY(_multi_bank_mode && address >= 0)) {
- address &= 0xffff;
- }
- if (address < -32768 || address > 65535) {
- std::stringstream ss;
- ss << "Addressing mode needs a word size argument. Argument was evaluated to " << address << ".";
- report_error(source_location, AssemblyErrorCodes::AddressingModeRequiresWordSizeArgument, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0] + 0] = static_cast<uint8_t>(address & 0xff);
- mutable_opcode_data[opcode_data.offset_to_data[0] + 1] = static_cast<uint8_t>(address >> 8);
-
- Section::Contents ending_instruction = Section::Contents::ContinueExecutionInstruction;
- auto &data = _section->generated_data(ending_instruction);
- for(decltype(opcode_data.total_size) i = 0; i < opcode_data.total_size; ++i) {
- data.push_back(mutable_opcode_data[i]);
- }
- }
- _program_counter.integer_value += static_cast<int32_t>(opcode_data.total_size);
-}
-
-const SyntaxToken *Assembler::parse_instruction(bool generate, const SyntaxToken *t, bool export_enabled)
-{
- assert(t->type == SyntaxTokenType::Instruction);
- const InstructionToken &instruction_token = *static_cast<const InstructionToken *>(t);
-
- // instructions are only allowed within code sections.
- bool instructions_allowed = _section != nullptr && _section->section_type == SectionType::Code;
- if (UNLIKELY(!instructions_allowed)) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Instructions must be in a code section.";
- report_fatal_error(instruction_token.source_location, AssemblyErrorCodes::CodeMustBeInCodeSection, ss.str());
- }
-
- // recursive data generation may not be safe
- if (_data_generation_depth != 0) {
- // this is an unrecoverable error
- std::stringstream ss;
- ss << "Recursive data generation isn't allowed.";
- report_fatal_error(instruction_token.source_location, AssemblyErrorCodes::RecursiveDataGenerationNotAllowed, ss.str());
- }
- ScopeCounter<uint32_t> sc(_data_generation_depth);
-
- InstructionType instruction = instruction_token.instruction;
- const InstructionOpCode &opcode_data = opcode(instruction, instruction_token.addressing_mode_index);
- Section::Contents ending_instruction = is_ending_instruction(instruction) ? Section::Contents::EndExecutionInstruction : Section::Contents::ContinueExecutionInstruction;
-
- if (UNLIKELY(generate && !_pseudo_instructions && opcode_data.category == InstructionCategory::Pseudo)) {
- std::stringstream ss;
- ss << "Pseudo instructions require the pseudo instruction mode to be enabled.";
- report_error(instruction_token.source_location, AssemblyErrorCodes::UseOfPseudoInstructionInStandardMode, ss.str());
- }
-
- t = consume_next_token(); // instruction
-
- // in the generation pass, the program counter is guaranteed to be an integer value
- // so there is no need to verify this
-
- // generate labels to instruction data
- for(int i = 0; i < 2; ++i) {
- if (UNLIKELY(instruction_token.has_instruction_data_label[i])) {
- // verify that the data label is valid for the addressing mode (can point to actual data)
- uint8_t data_size = argument_data_size(i, opcode_data);
- if (data_size != 0) {
- // exporting local variables is not allowed
- if (export_enabled && !instruction_token.global_data_label[i]) {
- std::stringstream ss;
- ss << variable_name(instruction_token.data_label_symbol_hash[i], instruction_token.global_data_label[i]) << " cannot be exported since it is local.";
- report_error(instruction_token.address_label_location[i], AssemblyErrorCodes::ExportingLocalIsNotAllowed, ss.str());
- }
-
- if (create_label(generate, instruction_token.data_label_symbol_hash[i], instruction_token.global_data_label[i], StorageType::Constant, instruction_token.address_label_location[i])) {
- Value &new_label = _current_pass.values.back();
- if (data_size == 1) {
- set_byte_offset(new_label, _program_counter.integer_value + argument_data_offset(i, opcode_data), 0);
- } else if (data_size == 2) {
- set_word_offset(new_label, _program_counter.integer_value + argument_data_offset(i, opcode_data), 0);
- } else {
- assert(false);
- }
- new_label.set_contains_address(true);
- if (export_enabled) {
- new_label.set_is_public(true);
- }
- }
- } else {
- std::stringstream ss;
- ss << "Addressing mode argument cannot have label to instruction data.";
- report_error(instruction_token.address_label_location[i], AssemblyErrorCodes::AddressingModeArgumentCannotHaveDataLabel, ss.str());
- }
- }
- }
-
- // copy the opcode to be able to modify it before writing it to the data stream
- uint8_t mutable_opcode_data[4];
- mutable_opcode_data[0] = opcode_data.op[0];
- mutable_opcode_data[1] = opcode_data.op[1];
- mutable_opcode_data[2] = opcode_data.op[2];
- mutable_opcode_data[3] = opcode_data.op[3];
-
- if (opcode_data.format != OpCodeFormat::OpcodeOnly) {
- assert(t->type == SyntaxTokenType::Expression);
- const ExpressionToken *expr1 = static_cast<const ExpressionToken *>(t);
- int32_t arg1 = evaluate_integer_expression_for_instruction_argument(generate, t);
- t = consume_next_token();
-
- switch (opcode_data.format)
- {
- case OpCodeFormat::OpcodeOnly:
- break;
- case OpCodeFormat::ByteArg:
- if (generate) {
- if (arg1 < -128 || arg1 > 255) {
- std::stringstream ss;
- ss << "Addressing mode needs a byte size argument. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresByteSizeArgument, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(arg1);
- }
- break;
- case OpCodeFormat::WordArg:
- if (generate) {
- if (UNLIKELY(_multi_bank_mode && arg1 >= 0)) {
- arg1 &= 0xffff;
- }
- if (arg1 < -32768 || arg1 > 65535) {
- std::stringstream ss;
- ss << "Addressing mode needs a word size argument. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresWordSizeArgument, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0] + 0] = static_cast<uint8_t>(arg1 & 0xff);
- mutable_opcode_data[opcode_data.offset_to_data[0] + 1] = static_cast<uint8_t>(arg1 >> 8);
- }
- break;
- case OpCodeFormat::OffsetArg:
- if (generate) {
- if (arg1 < -128 || arg1 > 127) {
- std::stringstream ss;
- ss << "Addressing mode needs an offset in range [-128..127]. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresOffsetSizeArgument, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(arg1);
- }
- break;
- case OpCodeFormat::OffsetAndByteArg:
- {
- assert(t->type == SyntaxTokenType::Expression);
- const ExpressionToken *expr2 = static_cast<const ExpressionToken *>(t);
- int32_t arg2 = evaluate_integer_expression_for_instruction_argument(generate, t);
- t = consume_next_token();
-
- if (generate) {
- if (arg1 < -128 || arg1 > 127) {
- std::stringstream ss;
- ss << "Addressing mode needs an offset in range [-128..127]. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresOffsetSizeArgument, ss.str());
- }
- if (arg2 < -128 || arg2 > 255) {
- std::stringstream ss;
- ss << "Addressing mode needs a byte size argument. Argument was evaluated to " << arg2 << ".";
- report_error(expr2->source_location, AssemblyErrorCodes::AddressingModeRequiresByteSizeArgument, ss.str());
- }
-
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(arg1);
- mutable_opcode_data[opcode_data.offset_to_data[1]] = static_cast<uint8_t>(arg2);
- }
- break;
- }
- case OpCodeFormat::InterruptModeArg:
- {
- if (generate) {
- if (arg1 < 0 || arg1 > 2) {
- std::stringstream ss;
- ss << "Addressing mode needs an interrupt mode in range [0..2]. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresByteSizeArgument, ss.str());
- }
- uint8_t opcode_part = 0;
- if (arg1 == 0) {
- opcode_part = 0x46;
- } else if (arg1 == 1) {
- opcode_part = 0x56;
- } else if (arg1 == 2) {
- opcode_part = 0x5e;
- }
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(opcode_part);
- }
- break;
- }
- case OpCodeFormat::BitAndRegisterArg:
- {
- if (generate) {
- if (arg1 < 0 || arg1 > 7) {
- std::stringstream ss;
- ss << "Addressing mode needs a bit argument in range [0..7]. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresBitArgument, ss.str());
- }
- size_t bit_argument_offset = opcode_data.total_size - 1; // the bit is always placed in the last opcode byte
- mutable_opcode_data[bit_argument_offset] |= static_cast<uint8_t>(arg1 << 3);
- }
- break;
- }
- case OpCodeFormat::BitAndOffsetArg:
- {
- assert(t->type == SyntaxTokenType::Expression);
- const ExpressionToken *expr2 = static_cast<const ExpressionToken *>(t);
- int32_t arg2 = evaluate_integer_expression_for_instruction_argument(generate, t);
- t = consume_next_token();
-
- if (generate) {
- if (arg1 < 0 || arg1 > 7) {
- std::stringstream ss;
- ss << "Addressing mode needs a bit argument in range [0..7]. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresBitArgument, ss.str());
- }
- if (arg2 < -128 || arg2 > 127) {
- std::stringstream ss;
- ss << "Addressing mode needs an offset in range [-128..127]. Argument was evaluated to " << arg2 << ".";
- report_error(expr2->source_location, AssemblyErrorCodes::AddressingModeRequiresOffsetSizeArgument, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(arg2);
- size_t bit_argument_offset = opcode_data.total_size - 1; // the bit is always placed in the last opcode byte
- mutable_opcode_data[bit_argument_offset] |= static_cast<uint8_t>(arg1 << 3);
- }
- break;
- }
- case OpCodeFormat::BranchOffsetArg:
- if (generate) {
- int32_t relative_offset = arg1 - (_program_counter.integer_value + 2);
- if (relative_offset < -128 || relative_offset > 127) {
- std::stringstream ss;
- ss << "Relative address out of range. Offset is " << relative_offset << " and needs to be in a [-128..127] range.";
- report_error(expr1->source_location, AssemblyErrorCodes::RelativeAddressOutOfRange, ss.str());
- }
- mutable_opcode_data[opcode_data.offset_to_data[0]] = static_cast<uint8_t>(relative_offset);
- }
- break;
- case OpCodeFormat::PageZeroArg:
- if (generate) {
- if ((arg1 & (~0b111000)) != 0) {
- std::stringstream ss;
- ss << "Zero page address must be 0, 8, 16, 24, 32, 40, 48, 56. Argument was evaluated to " << arg1 << ".";
- report_error(expr1->source_location, AssemblyErrorCodes::AddressingModeRequiresZeroPageArgument, ss.str());
- }
- mutable_opcode_data[0] |= static_cast<uint8_t>(mutable_opcode_data[0] | arg1);
- }
- break;
- }
- }
-
- if (generate) {
- for(decltype(opcode_data.total_size) i = 0; i < opcode_data.total_size; ++i) {
- _section->generated_data(ending_instruction).push_back(mutable_opcode_data[i]);
- }
- }
- _program_counter.integer_value += static_cast<int32_t>(opcode_data.total_size);
-
- return t;
-}
-
-#endif
-
bool Assembler::resolve_type_hash(bool generate, const TypeReference &type_reference, const SourceLocation &source_location, uint64_t &type_hash)
{
switch (type_reference.type) {
@@ 1385,9 852,10 @@ const SyntaxToken *Assembler::parse_defi
return t;
}
- if (!is_fixed_array)
+ if (!is_fixed_array) {
array_length = defined_array_elements;
-
+ }
+
success = true;
return t;
}
@@ 1575,9 1043,13 @@ const SyntaxToken *Assembler::parse_defi
// set the value base and offset (this will enable use of offsetof() and further offsetting
- value.offset_base = _program_counter.integer_value;
+ int32_t program_counter_before = _program_counter.integer_value;
+ value.offset_base = program_counter_before;
value.offset = 0;
+ auto &data = _section->generated_data();
+ size_t data_size_before = data.size();
+
bool is_array_type = define.type_reference.array_flag != ArrayFlag::Single;
bool success;
//t = parse_define_data(generate, success, t, array_updated_type, find_type(value.type_hash), is_array_type, array_length);
@@ 1597,6 1069,16 @@ const SyntaxToken *Assembler::parse_defi
return skip_to_end_of_define(t);
}
+ if (generate && _hex_source_writer != nullptr) {
+ uint32_t generated_size = static_cast<uint32_t>(data.size() - data_size_before);
+ uint32_t end_line = define.end_source_location.row + 1;
+ // if for some reason the end of the define is in a separate file, just print one line
+ if (define.source_location.file_index != define.end_source_location.file_index) {
+ end_line = define.source_location.file_index + 1;
+ }
+ _hex_source_writer->write_data(static_cast<uint32_t>(program_counter_before), &data[data_size_before], generated_size, define.source_location.file_index, define.source_location.row, end_line);
+ }
+
t = skip_to_end_of_define(t);
return t;
}
@@ 2401,6 1883,26 @@ const SyntaxToken *Assembler::parse_expo
}
+const SyntaxToken *Assembler::parse_processor(const SyntaxToken *t)
+{
+ ProcessorType processor_type = t->processor;
+ if (processor_type == ProcessorType::Unspecified) {
+ // pop the processor stack
+ _processor_stack.pop_back();
+ assert(!_processor_stack.empty());
+ _processor_type = _processor_stack.back();
+ _processor = _catalogue.processor(_processor_type);
+
+ } else {
+ _processor_type = processor_type;
+ _processor_stack.push_back(_processor_type);
+ _processor = _catalogue.processor(_processor_type);
+ }
+
+ return consume_next_token();
+}
+
+
const SyntaxToken *Assembler::parse_subroutine(bool generate, const SyntaxToken *t, bool export_enabled)
{
const SubroutineToken &subroutine = *static_cast<const SubroutineToken *>(t);
@@ 2472,6 1974,32 @@ const SyntaxToken *Assembler::parse_incb
t = consume_next_token(); // statement token
+ // parse filename
+ assert(t->type == SyntaxTokenType::Expression);
+ const ExpressionToken &filename_expression = *static_cast<const ExpressionToken *>(t);
+ const Value filename_value = evaluate_expression(generate, t);
+ t = consume_next_token();
+
+ if (!is_string(filename_value)) {
+ if (generate) {
+ std::stringstream ss;
+ ss << "Incbin expects a filename string but got " << to_string(filename_value.type) << ".";
+ report_error(filename_expression.source_location, AssemblyErrorCodes::ExpectedStringArgument, ss.str());
+ }
+ if (incbin.has_start_offset) {
+ // consume max size expression
+ t = consume_next_token();
+ }
+ if (incbin.has_max_size) {
+ // consume max size expression
+ t = consume_next_token();
+ }
+ return t;
+ }
+
+ std::string_view filename = dereference_string(filename_value);
+ uint64_t handle = _data_reader.queue_load(filename);
+
int32_t offset = 0;
int32_t max_size = std::numeric_limits<int32_t>::max();
@@ 2515,24 2043,58 @@ const SyntaxToken *Assembler::parse_incb
}
}
- try {
- int64_t data_size = static_cast<int64_t>(_data_reader.size(incbin.load_handle));
- int64_t data_start = clamp_by_value<int64_t>(static_cast<int64_t>(offset), core::Range<int64_t>{0LL, data_size});
- int64_t data_end = clamp_by_value<int64_t>(static_cast<int64_t>(offset) + max_size, core::Range<int64_t>{0LL, data_size});
- int32_t inserted_size = static_cast<int32_t>(data_end - data_start);
- _program_counter.integer_value += inserted_size;
+ size_t data_size = 0;
+ std::string error;
+ if (!_data_reader.size(handle, data_size, error)) {
if (generate) {
- const std::vector<uint8_t> &data = _data_reader.data(incbin.load_handle);
- auto §ion_data = _section->generated_data(Section::Contents::DataDefinition);
- section_data.insert(section_data.end(), data.begin() + data_start, data.begin() + data_end);
+ report_error(filename_expression.source_location, AssemblyErrorCodes::FailedToIncludeBinary, error.c_str());
}
- } catch (Exception &e) {
- // rethrow with link to problem
- throw AssemblyException(_used_files, incbin.source_location, AssemblyErrorCodes::FailedToIncludeBinary, e.message.c_str());
+ return t;
+ }
+
+ int64_t data_start = clamp_by_value<int64_t>(static_cast<int64_t>(offset), core::Range<int64_t>{0LL, static_cast<int64_t>(data_size)});
+ int64_t data_end = clamp_by_value<int64_t>(static_cast<int64_t>(offset) + max_size, core::Range<int64_t>{0LL, static_cast<int64_t>(data_size)});
+ int32_t inserted_size = static_cast<int32_t>(data_end - data_start);
+ _program_counter.integer_value += inserted_size;
+
+ if (generate) {
+ const std::vector<uint8_t> *data = nullptr;
+ if (!_data_reader.data(handle, data, error)) {
+ report_error(filename_expression.source_location, AssemblyErrorCodes::FailedToIncludeBinary, error.c_str());
+ return t;
+ }
+
+ auto §ion_data = _section->generated_data(Section::Contents::DataDefinition);
+ section_data.insert(section_data.end(), data->begin() + data_start, data->begin() + data_end);
}
return t;
}
+const SyntaxToken *Assembler::parse_include(bool generate, const SyntaxToken *t)
+{
+ t = consume_next_token(); // statement token
+
+ // parse filename
+ assert(t->type == SyntaxTokenType::Expression);
+ const ExpressionToken &filename_expression = *static_cast<const ExpressionToken *>(t);
+ const Value filename_value = evaluate_expression(generate, t);
+ t = consume_next_token();
+
+ if (!is_string(filename_value)) {
+ if (generate) {
+ std::stringstream ss;
+ ss << "Include expects a filename string but got " << to_string(filename_value.type) << ".";
+ report_error(filename_expression.source_location, AssemblyErrorCodes::ExpectedStringArgument, ss.str());
+ }
+ return t;
+ }
+
+ std::string_view filename_view = dereference_string(filename_value);
+ std::string filename(filename_view.data(), filename_view.size());
+ parse_file(generate, filename, &filename_expression.source_location);
+
+ return t;
+}
const SyntaxToken *Assembler::parse_statement_after_export(bool generate, const SyntaxToken *t, const SourceLocation &location)
{
@@ 2564,10 2126,11 @@ const SyntaxToken *Assembler::parse_stat
break;
case SyntaxTokenType::Instruction:
- t = parse_instruction(generate, t, export_enabled);
+ t = _processor->parse_instruction(*this, generate, t, export_enabled);
break;
case SyntaxTokenType::Incbin:
+ case SyntaxTokenType::Include:
case SyntaxTokenType::Align:
case SyntaxTokenType::Using:
case SyntaxTokenType::ForLoop:
@@ 2599,6 2162,7 @@ const SyntaxToken *Assembler::parse_stat
case SyntaxTokenType::ForLoopEnd:
case SyntaxTokenType::RepeatEnd:
case SyntaxTokenType::Return:
+ case SyntaxTokenType::Processor:
#if defined(_DEBUG)
case SyntaxTokenType::Debug:
#endif
@@ 2654,8 2218,13 @@ const SyntaxToken *Assembler::parse_stat
t = parse_export(generate, t);
break;
+ case SyntaxTokenType::Processor:
+ t = parse_processor(t);
+ break;
+
case SyntaxTokenType::Instruction:
- t = parse_instruction(generate, t, export_enabled);
+ assert(t->processor == _processor_type);
+ t = _processor->parse_instruction(*this, generate, t, export_enabled);
break;
case SyntaxTokenType::If:
@@ 2716,6 2285,10 @@ const SyntaxToken *Assembler::parse_stat
t = parse_incbin(generate, t);
break;
+ case SyntaxTokenType::Include:
+ t = parse_include(generate, t);
+ break;
+
case SyntaxTokenType::Optimize:
case SyntaxTokenType::StructDef:
case SyntaxTokenType::StructMember:
M jasm/assembling/function_pointer.h => jasm/assemble/function_pointer.h +2 -2
@@ 1,13 1,13 @@
#pragma once
-#include <assembling/value.h>
+#include <assemble/value.h>
namespace jasm {
class SINGLE_INHERITANCE Assembler;
struct ExpressionComponent;
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
constexpr uint32_t max_fixed_number_of_function_args = 3;
M jasm/assembling/functions.cpp => jasm/assemble/functions.cpp +2 -2
@@ 1,6 1,6 @@
#include "pch.h"
-#include <assembling/functions.h>
+#include <assemble/functions.h>
namespace jasm {
@@ 63,7 63,7 @@ const FunctionDesc &function_info(Functi
return desc[static_cast<int>(type)];
}
-const std::string_view to_string(FunctionType type)
+std::string_view to_string(FunctionType type)
{
static const std::string_view names[] = {
std::string_view("sizeof"),
M jasm/assembling/functions.h => jasm/assemble/functions.h +2 -2
@@ 2,7 2,7 @@
namespace jasm {
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
enum class FunctionType : uint8_t
@@ 63,7 63,7 @@ struct FunctionDesc {
/// Returns information about a function.
const FunctionDesc &function_info(FunctionType type);
-const std::string_view to_string(FunctionType type);
+std::string_view to_string(FunctionType type);
/// @}
M jasm/assembling/method_pointer.h => jasm/assemble/method_pointer.h +1 -1
@@ 1,6 1,6 @@
#pragma once
-#include <assembling/value.h>
+#include <assemble/value.h>
namespace jasm {
M jasm/assembling/methods.cpp => jasm/assemble/methods.cpp +3 -3
@@ 1,7 1,7 @@
#include "pch.h"
-#include <assembling/assembler_impl/assembler_impl.h>
-#include <assembling/methods.h>
+#include <assemble/assembler_impl/assembler_impl.h>
+#include <assemble/methods.h>
namespace jasm {
@@ 34,7 34,7 @@ const MethodDesc &method_info(MethodType
return desc[static_cast<int>(type)];
}
-const std::string_view to_string(MethodType type)
+std::string_view to_string(MethodType type)
{
static const std::string_view names[] = {
std::string_view("substring"),
M jasm/assembling/methods.h => jasm/assemble/methods.h +2 -2
@@ 6,7 6,7 @@ class SINGLE_INHERITANCE Assembler;
struct ExpressionComponent;
struct Value;
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
enum class StringProperties : uint8_t
@@ 89,7 89,7 @@ struct MethodDesc {
/// Returns information about a function.
const MethodDesc &method_info(MethodType type);
-const std::string_view to_string(MethodType type);
+std::string_view to_string(MethodType type);
/// @}
M jasm/assembling/scope_counter.h => jasm/assemble/scope_counter.h +1 -1
@@ 2,7 2,7 @@
namespace jasm {
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
template<typename T>
M jasm/assembling/symbol_environment.cpp => jasm/assemble/symbol_environment.cpp +1 -1
@@ 1,6 1,6 @@
#include "pch.h"
-#include <assembling/symbol_environment.h>
+#include <assemble/symbol_environment.h>
#include <core/strings/murmur_hash.h>
namespace jasm
M jasm/assembling/symbol_environment.h => jasm/assemble/symbol_environment.h +2 -2
@@ 1,13 1,13 @@
#pragma once
-#include <parsing/source_location.h>
#include <core/collections/hash_map.h>
#include <core/collections/null_hash_compare.h>
+#include <tokenize/source_location.h>
namespace jasm
{
-/// @addtogroup assembling
+/// @addtogroup assemble
/// @{
/// This contains the connection between a local name and a combined name.
M jasm/assembling/type_description.h => jasm/assemble/type_description.h +3 -3
@@ 1,8 1,8 @@
#pragma once
-#include <assembling/value.h>
-#include <parsing/syntax_parser.h>
-#include <parsing/tokenizer.h>
+#include <assemble/value.h>
+#include <syntax/syntax_parser.h>
+#include <tokenize/tokenizer.h>
namespace jasm
{
M jasm/assembling/value.cpp => jasm/assemble/value.cpp +2 -2
@@ 1,6 1,6 @@
#include "pch.h"
-#include <assembling/value.h>
+#include <assemble/value.h>
#include <core/exceptions/exception.h>
#include <cstring>
#include <strings/string_repository.h>
@@ 9,7 9,7 @@ namespace jasm {
using namespace core;
-const std::string_view to_string(ValueType type)
+std::string_view to_string(ValueType type)
{
assert(type < ValueType::NumTypes);
switch(type)
M jasm/assembling/value.h => jasm/assemble/value.h +7 -7
@@ 1,14 1,14 @@
#pragma once
#include <algorithm>
-#include <assembling/functions.h>
-#include <assembling/methods.h>
-#include <assembling/symbol_environment.h>
+#include <assemble/functions.h>
+#include <assemble/methods.h>
+#include <assemble/symbol_environment.h>
#include <core/collections/split_vector.h>
#include <core/strings/murmur_hash.h>
-#include <parsing/storage_type.h>
-#include <parsing/token_chain.h>
-#include <parsing/types.h>
+#include <syntax/storage_type.h>
+#include <tokenize/types.h>
+#include <utility/token_chain.h>
namespace jasm
{
@@ 69,7 69,7 @@ enum class ValueType : uint8_t
};
-const std::string_view to_string(ValueType type);
+std::string_view to_string(ValueType type);
/// This is the POD part of a value.
struct StaticValue
M +2 -0
@@ 4,6 4,8 @@
<title>jAsm Documentation</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="description" content="This is the documentation for the jAsm assembler.">
<meta name="keywords" content="jAsm,6502,z80,assembler,asm,cross-assembler">
<meta name="author" content="Jonas Hultén">
<link rel="shortcut icon" href="images/favicon.ico">
<link href="jasm.css" rel="stylesheet">
</head>
M jasm/docs/jasm.md +187 -57
@@ 42,22 42,24 @@ This documentation covers the language a
* [Compiling jAsm](#compiling-jasm)
* [Fetching Source Code](#fetching-source-code)
* [Compiling Using CMake](#compiling-using-cmake)
- * [Compiling Using Visual Studio](#compiling-using-vs)
* [Starting jAsm](#starting-jasm)
* [Bank Mode](#bank-mode)
* [Predefined Constants](#predefined-constants)
* [Symbol Dumps](#symbol-dumps)
+ * [Hex Output](#hex-output)
* [Binary Header](#binary-header)
* [Include Paths](#include-paths)
* [Max Errors](#max-errors)
* [Output Files and Sections](#output-files-and-sections)
- * [Verboseness](#verboseness)
+ * [Default Processor](#default-processor)
* [Pseudo Instructions](#pseudo-instructions)
* [6502 Pseudo Instructions](#6502-pseudo-instructions)
* [Z80 Pseudo Instructions](#z80-pseudo-instructions)
+ * [Verboseness](#verboseness)
* [Return Codes](#return-codes)
* [Language Reference](#language-reference)
* [Input Format](#input-format)
+ * [Selecting Processor](#selecting-processor)
* [Comments](#comments)
* [Assembler Instruction Syntax](#assembler-instruction-syntax)
* [Constants](#constants)
@@ 122,6 124,11 @@ jAsm supports all regular instructions o
lda #0
sta $d020
+Due to the large amount of source code with upper case instruction keywords, a python script is provided to convert upper case keywords in all .asm files in a directory. Run that like this.
+
+ [text]
+ python3 tools/convert_6502_keyword_case.py <my_source_directory>
+
<div id="z80"></div>
## Z80
@@ 131,10 138,10 @@ jAsm supports all regular instructions o
ld a, 0
ld (hl), a
-Due to the large amount of source code with upper case instruction keywords, a python script is provided to convert upper case keywords in all .asm files in a directory. Run that like this.
+There's also a script to convert Z80 uppercase keywords to lowercase. Run that like this.
[text]
- python3 jasm-z80/convert_z80_keyword_case.py <my_source_directory>
+ python3 tools/convert_z80_keyword_case.py <my_source_directory>
<div id="starter-guide"></div>
# Starter Guide
@@ 144,6 151,8 @@ Due to the large amount of source code w
We'll start by creating a small program in a text file.
[6502]
+ processor "6502"
+
section code, "main", $8000
{
inc $d020
@@ 153,7 162,7 @@ We'll start by creating a small program
Save this to a file named main.jasm. Use utf-8 format, because this is what jAsm expects. 7-bit ASCII is also ok since that is compatible with the utf-8 format. Now we'll assemble it into a binary. Open a command line window and change the current directory to where the main.jasm file is. Type this on the command line.
[text]
- jasm-6502 -hla main.jasm main.prg
+ jasm -hla main.jasm main.prg
Now you have a program that changes the border color on a Commodore 64. Load it into an emulator or onto a real machine.
@@ 173,6 182,8 @@ The border color changes.
If you want to start it on a Commodore 64 with a BASIC line, you need to add the necessary data to produce a SYS line at the BASIC start. This is specific to the Commodore BASIC v2. This example shows how to do that in jAsm.
[6502]
+ processor "6502"
+
section code, "main", $0801
{
define word = .next_basic_line // next BASIC line
@@ 200,6 211,8 @@ Stuff written after `[6502]|//` are comm
This BASIC line thing will be used a lot in programs since almost all programs loaded from disk will need it. Let's break out this code into a handy macro that we can reuse. The macro will need two arguments, one is the line number and one is the address to start the program from.
[6502]
+ processor "6502"
+
macro basic_sys_line(.line_number, .sys_address)
{
define word = .next_basic_line // next BASIC line
@@ 230,6 243,8 @@ The main section of our example looks a
Move the macro code into a file called macros.jasm and place it where main.jasm lies. We can now include the macros in main.jasm.
[6502]
+ processor "6502"
+
include "macros.jasm"
section code, "main", $0801
@@ 247,6 262,8 @@ Move the macro code into a file called m
The border color changing address isn't exactly self explanatory. The BASIC start address is also a naked constant that isn't exactly self explained. Let's make this a bit better.
[6502]
+ processor "6502"
+
include "macros.jasm"
const BASIC_START = $0801
@@ 264,6 281,8 @@ The border color changing address isn't
I use uppercase characters for fixed address constants (basically any naked constant) to make it easy to identify them. `[6502]|BASIC_START` and `[6502]|BORDER_COLOR` can now be used instead of the naked constants. Let's move the constants out into their own file as well. Call this c64.jasm since they describe constants specific to Commodore 64. We'll include this as well in the program.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 288,6 307,8 @@ Now, what if we wanted to port this to V
Now, what we need is a way to include either the c64.jasm or vic20.jasm file based on an option somewhere. Let's add the selection first.
[6502]
+ processor "6502"
+
include "macros.jasm"
if (C64_BUILD) {
include "c64.jasm"
@@ 310,8 331,8 @@ Now, what we need is a way to include ei
The `[6502]|if` statement wants a boolean expression within the parentheses and if true the first block of code is used, otherwise the second block is used. We can feed constants from the command line to solve this. The command line option is `[text]|-d` and it needs to be followed by an assignment. In this case we want to assign `[6502]|C64_BUILD` to `[6502]|true` or `[6502]|false`.
[text]
- jasm-6502 -d C64_BUILD=true main.jasm main.prg
- jasm-6502 -d C64_BUILD=false main.jasm main.prg
+ jasm -d C64_BUILD=true main.jasm main.prg
+ jasm -d C64_BUILD=false main.jasm main.prg
<div id="starter-guide-definining-data"></div>
## Defining Data
@@ 324,6 345,8 @@ Let's try a hello world example. We'll d
Now we'll add the loop to print the text.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 352,6 375,8 @@ The define now has a name before the equ
This works but is hard to read. It isn't obvious where the loop starts and ends unless we read the instructions. Let's improve it using indentation.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 378,6 403,8 @@ This works but is hard to read. It isn't
This is better but can be improved further. jAsm supports an automatic `[6502]|@loop` label at the beginning of a scope defined by curly braces.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 407,6 434,8 @@ It's now much easier to read the loop an
If we want to print more text we need to move the loop into a subroutine which can be called with a jsr instruction and some parameters in registers.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 450,10 479,10 @@ If we want to print more text we need to
`[6502]|*` in the subroutine represents the current program counter. `[6502]|* + 1` points one byte into the next instruction, which is where the instruction argument is. All is well, except that it doesn't assemble!
[text]
- main.jasm(23,7) : Error 3004 : Reference to undefined symbol .addr
- main.jasm(24,7) : Error 3004 : Reference to undefined symbol .addr
- main.jasm(24,13) : Error 3000 : Operator + is not defined for left hand side unknown type.
- main.jasm(25,7) : Error 3004 : Reference to undefined symbol .size
+ main.jasm(25,7) : Error 3004 : Reference to undefined symbol .addr
+ main.jasm(26,7) : Error 3004 : Reference to undefined symbol .addr
+ main.jasm(26,13) : Error 3000 : Operator + is not defined for left hand side unknown type.
+ main.jasm(27,7) : Error 3004 : Reference to undefined symbol .size
<div id="starter-guide-declaring-symbols"></div>
## Declaring Symbols
@@ 463,6 492,8 @@ There is something wrong with `[6502]|.a
To solve this we can declare the symbol names in the subroutine scope but define the constants inside the loop. This is the working subroutine.
[6502]
+ processor "6502"
+
// -> xa: address to text
// -> y: size of text
subroutine print_text
@@ 492,6 523,8 @@ To solve this we can declare the symbol
There is a more intuitive way to declare the `[6502]|.addr` and `[6502]|.size` addresses. Instruction data labels can point directly to the instruction argument by placing a label definition between the instruction and the argument.
[6502]
+ processor "6502"
+
// -> xa: address to text
// -> y: size of text
subroutine print_text
@@ 519,6 552,8 @@ There is a more intuitive way to declare
This subroutine can be reused so let's move it to its own file. Name a new file screen\_io.jasm and paste the subroutine into it. Now we'll modify the main file to include this new file. Note that we now must include the file inside the section because otherwise generated code or data would lie outside any section and that isn't allowed. Only code sections can contain code or data. The other include files only contain constant definitions and macros and they don't directly produce any code or data themselves. That's why they can be outside a section.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 544,6 579,8 @@ This subroutine can be reused so let's m
Self modifying code is handy and can improve efficiency but it doesn't work if the code is in a cartridge ROM, because it can't be modified. Let's try modifying the code to use the zero page instead. To do this we need to reserve some space for variables in the zero page area. This is done with a bss section. BSS stands for "Block Started by Symbol" and means a static memory block that is part of the program, but without its content stored in the executable file. The bss section doesn't generate any code or data, it just reserves uninitialized space. I reserved the last 5 bytes in the zero page area from $fb to, but not including, $100.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 600,6 637,8 @@ Now we need to modify the print subrouti
It would also be nice to avoid having to specify the length of the string when printing it. The code became a bit kludgy when swapping registers. We can solve this by removing the need for the size argument. If we zero terminate the string we can get rid of it (or swap argument registers).
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 653,6 692,8 @@ One thing that isn't really great is tha
This is what main.jasm looks like after the change.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 753,6 794,8 @@ The reference to the print subroutine mu
If `[6502]|print_text` is used a lot in one place it is also possible to specify that a namespace should be used in a scope. As long as other names don't start to collide, this is just as good.
[6502]
+ processor "6502"
+
include "macros.jasm"
include "c64.jasm"
@@ 822,7 865,7 @@ Accessing the print\_text subroutine in
jAsm can assist debugging in the VICE emulator by exporting the names of addresses for use in the emulator. Add [--dump-vice-symbols](#symbol-dumps) and a filename to the command line arguments to export this information.
[text]
- jasm-6502 --dump-vice-symbols main.vs main.jasm main.prg
+ jasm --dump-vice-symbols main.vs main.jasm main.prg
Now, a symbol file will be created called `[text]|main.vs`. Let's start the emulator (install it first if you don't have it) and use the file.
@@ 934,12 977,12 @@ Now you know the basics of jAsm and shou
<div id="fetching-source-code"></div>
## Fetching Source Code
-You need to fetch the source code from BitBucket to get started. If you have a command line Mercurial client you can clone the repository like this.
+You need to fetch the source code from SourceHut to get started. If you have a command line Mercurial client you can clone the repository like this.
[text]
- hg clone ssh://hg@bitbucket.org/bjonte/jasm
-
-jAsm compiles using CMake and Clang or using Code::Blocks or Visual Studio.
+ hg clone https://hg.sr.ht/~bjonte/jasm
+
+jAsm compiles using CMake and Clang.
<div id="compiling-using-cmake"></div>
## Compiling Using CMake
@@ 952,7 995,7 @@ To build with CMake you need CMake 3.5,
Clone the repository into a directory called 'jasm' and build it like this.
[text]
- hg clone ssh://hg@bitbucket.org/bjonte/jasm
+ hg clone https://hg.sr.ht/~bjonte/jasm
cd jasm
export CXX=/usr/bin/clang++
mkdir build
@@ 968,26 1011,19 @@ If you want to cross compile binaries fo
Cross compile like this.
[text]
- hg clone ssh://hg@bitbucket.org/bjonte/jasm
+ hg clone https://hg.sr.ht/~bjonte/jasm
cd jasm
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../win64_cross_compile_toolchain.txt -DCMAKE_BUILD_TYPE=Release ..
make
-You will find the binaries in build/jasm-6502 and build/jasm-z80. You will also need the MingW dynamic link libraries found here in Linux Mint.
+You will find the binaries in build/jasm. You will also need the MingW dynamic link libraries found here in Linux Mint.
[text]
/usr/lib/gcc/x86_64-w64-mingw32/7.3-win32/libgcc_s_seh-1.dll
/usr/lib/gcc/x86_64-w64-mingw32/7.3-win32/libstdc++-6.dll
-<div id="compiling-using-vs"></div>
-## Compiling Using Visual Studio
-
-<i>The Visual Studio solution is no longer actively maintained and may not work. Cross compiling on Linux is the supported method to build Windows binaries.</i>
-
-Download Visual Studio 2015 from www.microsoft.com and install it. Double click on the `[text]|jasm.sln` file to open the project. Select the `[text]|Release` configuration and build the solution. You will get a `[text]|jasm.exe` binary in `[text]|x64\Release`.
-
<div id="starting-jasm"></div>
# Starting jAsm
@@ 996,12 1032,7 @@ Download Visual Studio 2015 from www.mic
jAsm is a command line tool. It will print its arguments if started without any. Basically it needs an input file and an output file.
[text]
- jasm-6502 input.jasm output.bin
-
-If you are assembling for Z80, use that version of the assembler instead.
-
- [text]
- jasm-z80 input.jasm output.bin
+ jasm input.jasm output.bin
There are some flags to tweak how the assembler behaves.
@@ 1011,7 1042,7 @@ There are some flags to tweak how the as
When working with several memory banks it is handy to place them after each other in memory. That way it is possible to check which bank code or data belongs to just looking at the address. For example, cartridge bank 0 could be located at $08000-$0a000 and bank 1 at $18000-$1a000. However, jAsm will generate an error when trying to reference bank 1 in data definitions or instructions because the addresses exceeds 16 bits. This can be overridden with the `[text]|--bank-mode` flag, which automatically truncates long addresses.
[text]
- jasm-6502 --bank-mode input.jasm output.bin
+ jasm --bank-mode input.jasm output.bin
_A shortcut alternative is `[text]|-bm`._
@@ 1023,8 1054,8 @@ This also have implications on the high
You can instruct the assembler to create some initial constants that can be accessed in the source code with the `[text]|--define` flag.
[text]
- jasm-6502 --define INFINITE_LIVES=true --define STARTING_LIVES=3 input.jasm output.bin
- jasm-6502 --define DEFAULT_NAME=bobo input.jasm output.bin
+ jasm --define INFINITE_LIVES=true --define STARTING_LIVES=3 input.jasm output.bin
+ jasm --define DEFAULT_NAME=bobo input.jasm output.bin
You can feed it with integers, booleans and strings, like in the examples above.
@@ 1047,31 1078,66 @@ The constants and variables in the assem
Dump jAsm symbols like this.
[text]
- jasm-6502 --dump-symbols symbols.txt input.jasm output.bin
+ jasm --dump-symbols symbols.txt input.jasm output.bin
_A shortcut alternative is `[text]|-ds`._
Dump VICE symbols like this.
[text]
- jasm-6502 --dump-vice-symbols symbols.vs input.jasm output.bin
+ jasm --dump-vice-symbols symbols.vs input.jasm output.bin
_A shortcut alternative is `[text]|-dv`._
Dump No$GBA symbols like this.
[text]
- jasm-6502 --dump-gba-symbols symbols.sym input.jasm output.bin
+ jasm --dump-gba-symbols symbols.sym input.jasm output.bin
_A shortcut alternative is `[text]|-dg`._
+<div id="hex-output"></div>
+## Hex Output
+
+The assembled program can be written as a hex file interleaved with embedded source lines that produced the output to help understanding what the assembler produced.
+
+Write hex output like this.
+
+ [text]
+ jasm --dump-hex hex_output.txt input.jasm output.bin
+
+_A shortcut alternative is `[text]|-dh`._
+
+The file will output all source lines that generate data. The first column is the program counter, then up to four columns of binary data. This is followed by a line number and then the source code that produced the generated data.
+
+ [text]
+ ./source/main_loop.jasm
+ --------------------------------------------------------------------------------
+ 0400: 20 17 04 7: jsr setup_cpu
+ 8:
+ 0403: 20 46 04 9: jsr blank_screen
+ 10:
+ 0406: 20 00 1f 11: jsr mmu::setup
+ 0409: 20 6b 04 12: jsr init_reset_vector
+
+When the source file changes, the file name and a line with dashes will be added. In case there is a longer jump in line numbers or a jump backwards, a partially dashed line is printed.
+
+ [text]
+ 046b: ad 06 d5 51: lda MMURCR
+ 046e: 48 52: pha
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+ 046f: ad 06 d5 67: lda MMURCR
+ 0472: 29 f7 68: and #~MMURCR_COMMON_TOP
+ 0474: 09 04 69: ora #MMURCR_COMMON_BOTTOM
+ 0476: 8d 06 d5 70: sta MMURCR
+
<div id="binary-header"></div>
## Binary Header
By default, jAsm outputs only the binary data without any header. To generate a program file for Commodore 64 that can be loaded from BASIC, a two byte header must be added containing the load address in little endian format. You can add this header using `[text]|--header-little-endian-address`.
[text]
- jasm-6502 --header-little-endian-address input.jasm output.prg
+ jasm --header-little-endian-address input.jasm output.prg
_A shortcut alternative is `[text]|-hla`._
@@ 1081,7 1147,7 @@ By default, jAsm outputs only the binary
You can add include paths using the `[text]|--include-dir` flag. jAsm will look in these for included files.
[text]
- jasm-6502 --include-dir some/dir --include-dir other/dir input.jasm output.bin
+ jasm --include-dir some/dir --include-dir other/dir input.jasm output.bin
_A shortcut alternative is `[text]|-i`._
@@ 1091,7 1157,7 @@ You can add include paths using the `[te
With the `[text]|--max-errors` flag, you can specify the number of errors that will be printed before jAsm stops assembling.
[text]
- jasm-6502 --max-errors 4 input.jasm output.bin
+ jasm --max-errors 4 input.jasm output.bin
_A shortcut alternative is `[text]|-me`._
@@ 1101,37 1167,36 @@ With the `[text]|--max-errors` flag, you
The default output mode will merge all code sections into one big binary and pad the inbetween space with zero. With the flag `[text]|--output-multiple-files`, this can be changed to store one file per section instead. Each file will be named after the output file but add the section name before the file extension.
[text]
- jasm-6502 --output-multiple-files input.jasm output.bin
+ jasm --output-multiple-files input.jasm output.bin
_A shortcut alternative is `[text]|-om`._
You can choose to have jAsm name the files after the sections by not specifying an output file name.
[text]
- jasm-6502 --output-multiple-files input.jasm
+ jasm --output-multiple-files input.jasm
You may want to add an extension to the section names when using them as file names. Use the option `[text]|--file-extension` to do that.
[text]
- jasm-6502 --output-multiple-files --file-extension prg input.jasm
+ jasm --output-multiple-files --file-extension prg input.jasm
_A shortcut alternative is `[text]|-ext`._
-<div id="verboseness"></div>
-## Verboseness
-
-jAsm supports several levels of output during assembly. This is controlled by the `[text]|-v0`, `[text]|-v1`, `[text]|-v2` and `[text]|-v3` flags.
+<div id="default-processor"></div>
+## Default Processor
+
+You can set the default processor to use when assembling the source code using the option `[text]|--processor`. If you do this you won't need to specify the processor in the source code, unless you need to switch it.
[text]
- jasm-6502 -v2 input.jasm output.bin
-
-<table>
- <tr><th>Flag</th><th>Meaning</th></tr>
- <tr><td><code>[text]|-v0</code></td><td>Show errors</td></tr>
- <tr><td><code>[text]|-v1</code></td><td>Show errors and warnings</td></tr>
- <tr><td><code>[text]|-v2</code></td><td>Show errors, warnings, printouts and general information</td></tr>
- <tr><td><code>[text]|-v3</code></td><td>Show errors, warnings, general information and debugging information</td></tr>
-</table>
+ jasm --processor 6502 input.jasm output.bin
+
+or
+
+ [text]
+ jasm --processor z80 input.jasm output.bin
+
+_A shortcut alternative is `[text]|-p`._
<div id="pseudo-instructions"></div>
## Pseudo Instructions
@@ 1166,6 1231,22 @@ These are the pseudo instructions for Z8
They are implemented using two instructions under the hood. First the high register part is loaded and then the low.
+<div id="verboseness"></div>
+## Verboseness
+
+jAsm supports several levels of output during assembly. This is controlled by the `[text]|-v0`, `[text]|-v1`, `[text]|-v2` and `[text]|-v3` flags.
+
+ [text]
+ jasm -v2 input.jasm output.bin
+
+<table>
+ <tr><th>Flag</th><th>Meaning</th></tr>
+ <tr><td><code>[text]|-v0</code></td><td>Show errors</td></tr>
+ <tr><td><code>[text]|-v1</code></td><td>Show errors and warnings</td></tr>
+ <tr><td><code>[text]|-v2</code></td><td>Show errors, warnings, printouts and general information</td></tr>
+ <tr><td><code>[text]|-v3</code></td><td>Show errors, warnings, general information and debugging information</td></tr>
+</table>
+
<div id="return-codes"></div>
## Return Codes
@@ 1183,6 1264,55 @@ This section documents the entire syntax
jAsm uses Unicode utf-8 encoded text files only. If you provide something that can't be interpreted as utf-8, an error will be returned.
+<div id="selecting-processor"></div>
+
+To assemble instructions jAsm needs to know what processor to target. This is done by either specifying the processor using [command line flags](#default-processor) or by a keyword in the source code. Specify the processor in a source file like this.
+
+ [6502]
+ processor "6502"
+
+After this statement, the assembler can handle 6502 processor instructions. You can switch processor in a source file several times.
+
+ [6502]
+ processor "6502"
+ rts
+ processor "z80"
+ ret
+
+It is also possible to momentarily change the processor and switch back to whatever it was before. The `[6502]|processor pop` statement is used to change back to the previously set processor.
+
+ [6502]
+ processor "6502"
+ rts
+ processor "z80"
+ ret
+ processor pop
+ rts
+ processor pop
+
+Included files inherit the processor from the file with the include statement but the processor set in the included file won't affect the file where the include statement is.
+
+Suppose we have a file named test.jasm:
+
+ [z80]
+ // processor 6502 inherited from main.jasm
+ rts
+
+ processor "z80"
+ // processor is now set to z80
+ ret
+
+and a file named main.jasm:
+
+ [6502]
+ processor "6502"
+ // processor is now set to 6502
+ include "test.jasm"
+ // processor is still 6502
+ lda #0
+
+When including `[text]|test.jasm`, the `[6502]|rts` instruction is assembled using 6502 because it was inherited from `[text]|main.jasm`. The `[z80]|ret` instruction is assembled as z80 since the processor was changed in the included file before the instruction. After the included file the processor is 6502 since the included file won't affect the file it is included from.
+
<div id="comments"></div>
## Comments
M jasm/docs/syntax_highlight.py +1 -1
@@ 43,7 43,7 @@ def replace_with_token(text, replace_map
return text
def syntax_highlight_code(code_class, text):
- keywords = re.compile(r'\b(address|align|basic|bss|byte|code|const|declare|define|dynamic|elif|else|enum|export|fill|for|function|if|import|incbin|include|long|macro|mapping|module|namespace|optimize|part|reserve|return|section|struct|subroutine|using|var|word)\b')
+ keywords = re.compile(r'\b(address|align|basic|bss|byte|code|const|declare|define|dynamic|elif|else|enum|export|fill|for|function|if|import|incbin|include|long|macro|mapping|module|namespace|optimize|part|pop|processor|reserve|return|section|struct|subroutine|using|var|word)\b')
functions = re.compile(r'\b(abs|acos|asin|atan|atan2|ceil|clamp|cos|cosh|degrees|exp|float|floor|format|hexstring|int|lerp|log|log10|logn|max|min|modulo|offsetof|pow|print|radians|remainder|round|select|sin|sinh|sizeof|sqrt|static_assert|string|symbol|tan|tanh|unicode|uppercase|lowercase)\b')
instructions_6502 = re.compile(r'\b(adc|and|asl|bcc|bcs|beq|bit|bmi|bne|bpl|brk|bvc|bvs|clc|cld|cli|clv|cmp|cpx|cpy|dec|dex|dey|eor|inc|inx|iny|jmp|jsr|lda|ldx|ldy|lsr|nop|ora|pha|php|pla|plp|rol|ror|rti|rts|sbc|sec|sed|sei|sta|stx|sty|tax|tay|tsx|txa|txs|tya|bhs|blt)\b')
instructions_z80 = re.compile(r'\b(adc|add|and|bit|call|ccf|cp|cpd|cpdr|cpi|cpir|cpl|daa|dec|di|djnz|ei|ex|exx|halt|im|in|inc|ind|indr|ini|inir|jp|jr|ld|ldd|lddr|ldi|ldir|neg|nop|or|otdr|otir|out|outd|outi|pop|push|res|ret|reti|retn|rl|rla|rlc|rlca|rld|rr|rra|rrc|rrca|rrd|rst|sbc|scf|set|sla|sra|srl|sub|xor)\b')
M jasm/environment/command_line_args.cpp +44 -6
@@ 8,6 8,7 @@ using namespace core;
CommandLineArgs::CommandLineArgs(std::string version)
: verbose_level(ErrorLevel::Errors)
+ , default_processor(ProcessorType::Unspecified)
, max_errors(20)
, multiple_output_files(false)
, section_names_as_file_names(false)
@@ 44,14 45,14 @@ bool CommandLineArgs::parse(int argc, co
}
// verify that there is exactly one '=' in that string
std::string define = argv[i];
- if (std::count(define.begin(), define.end(), L'=') != 1) {
+ if (std::count(define.begin(), define.end(), '=') != 1) {
error() << arg << " option must have NAME=VALUE argument\n";
print_usage(argv[0]);
return false;
}
// split string on equal sign
- auto equal_position = std::find(define.begin(), define.end(), L'=');
+ auto equal_position = std::find(define.begin(), define.end(), '=');
std::string symbol(define.begin(), equal_position);
std::string value_str(equal_position + 1, define.end());
bool boolean_true = value_str == "true";
@@ 104,6 105,15 @@ bool CommandLineArgs::parse(int argc, co
}
gba_sym_dump_file = argv[i];
+ } else if (arg == "-dh" || arg == "--dump-hex") {
+ ++i;
+ if (i == argc) {
+ error() << "Missing " << arg << " argument\n";
+ print_usage(argv[0]);
+ return false;
+ }
+ output_hex_file = argv[i];
+
} else if (arg == "-os" || arg == "--output-single-file") {
multiple_output_files = false;
@@ 142,6 152,26 @@ bool CommandLineArgs::parse(int argc, co
}
file_extension = argv[i];
+ } else if (arg == "-p" || arg == "--processor") {
+ ++i;
+ if (i == argc) {
+ error() << "Missing " << arg << " processor argument\n";
+ print_usage(argv[0]);
+ return false;
+ }
+ if (!is_processor(argv[i], default_processor)) {
+ error() << "Invalid processor '" << argv[i] << "' specified.\n";
+ error() << "Possible values are ";
+ for(uint8_t p = 1; p < static_cast<uint8_t>(ProcessorType::NumProcessors); ++p) {
+ if (p != 1) {
+ error() << ", ";
+ }
+ error() << to_string(static_cast<ProcessorType>(p));
+ }
+ error() << '\n';
+ return false;
+ }
+
} else if (arg == "-pi" || arg == "--pseudo-instructions") {
pseudo_instructions = true;
@@ 221,15 251,18 @@ void CommandLineArgs::print_usage(const
error() << " -d NAME=VALUE\n";
error() << " --define NAME=VALUE\n";
error() << " Add predefined symbol (integer, string or boolean value)\n\n";
+ error() << " -dg FILE\n";
+ error() << " --dump-gba-symbols FILE\n";
+ error() << " Dump No$GBA style symbols to the specified file\n\n";
+ error() << " -dh FILE\n";
+ error() << " --dump-hex FILE\n";
+ error() << " Dump hex output interleaved with source code to the specified file\n\n";
error() << " -ds FILE\n";
error() << " --dump-symbols FILE\n";
error() << " Dump symbols to the specified file\n\n";
error() << " -dv FILE\n";
error() << " --dump-vice-symbols FILE\n";
error() << " Dump VICE symbol commands to the specified file\n\n";
- error() << " -dg FILE\n";
- error() << " --dump-gba-symbols FILE\n";
- error() << " Dump No$GBA style symbols to the specified file\n\n";
error() << " -ext EXT\n";
error() << " --file-extension EXT\n";
error() << " File extension to use when naming files after sections\n\n";
@@ 241,13 274,18 @@ void CommandLineArgs::print_usage(const
error() << " Add include directory\n\n";
error() << " -me NUM\n";
error() << " --max-errors NUM\n";
- error() << " Max number of errors before aborting assembler generation pass (default is 20)\n\n";
+ error() << " Max number of errors before aborting assembler generation pass\n\n";
+ error() << " (default is 20)\n\n";
error() << " -om\n";
error() << " --output-multiple-files\n";
error() << " Assemble an output file per section\n\n";
error() << " -os\n";
error() << " --output-single-file\n";
error() << " Assemble a single output file (default)\n\n";
+ error() << " -p PROCESSOR\n";
+ error() << " --processor PROCESSOR\n";
+ error() << " Sets the default processor. This removes the need to specify the\n";
+ error() << " processor in the source code.\n\n";
error() << " -pi\n";
error() << " --pseudo-instructions\n";
error() << " Adds support for additional pseudo instructions or addressing modes to\n";
M jasm/environment/command_line_args.h +3 -0
@@ 1,6 1,7 @@
#pragma once
#include <core/environment/log.h>
+#include <processor/processor.h>
namespace jasm
{
@@ 20,11 21,13 @@ public:
std::string vice_dump_file; ///< File to dump VICE symbol commands to, or empty.
std::string gba_sym_dump_file; ///< File to dump NO$GBA style symbol to, or empty.
std::string file_extension; ///< File extension to use if files are named after sections.
+ std::string output_hex_file; ///< File to dump hex output with interleaved source to.
std::vector<std::string> include_dirs; ///< List of include directories.
std::vector<std::pair<std::string, bool>> predefined_booleans; ///< List of predefined constants and values.
std::vector<std::pair<std::string, int32_t>> predefined_integers; ///< List of predefined constants and values.
std::vector<std::pair<std::string, std::string>> predefined_strings; ///< List of predefined constants and values.
core::ErrorLevel verbose_level;
+ ProcessorType default_processor;
int32_t max_errors; ///< Max number of errors before aborting assembler generation pass.
bool multiple_output_files; ///< True means use one file per section.
bool section_names_as_file_names; ///< True means name files after sections.
M jasm/exceptions/assembly_exception.h +1 -1
@@ 2,7 2,7 @@
#include <core/exceptions/exception.h>
#include <exceptions/error_codes.h>
-#include <parsing/source_location.h>
+#include <tokenize/source_location.h>
namespace jasm
{
M jasm/exceptions/error_codes.h +10 -4
@@ 12,6 12,8 @@ namespace jasm
/// instead.
enum class AssemblyErrorCodes
{
+ Ok = 0,
+
// tokenizer
UnexpectedCharacter = 1000,
AlphaFollowingNumberLiteral,
@@ 21,13 23,13 @@ enum class AssemblyErrorCodes
TooLongCharacterConstant,
MultilineCommentWasNotTerminated,
MissingClosingStringQuote,
- StringTooLong, // not used!
+ Unused1, // not used!
IllegalCharacterInBinaryConstant,
IllegalCharacterInHexConstant,
CantFindIncludeFile,
- RecursiveIncludes,
- ExpectedPathString,
- Unused1, // not used!
+ UnmatchedProcessorPop,
+ InvalidProcessorName,
+ ExpectedProcessorNameOrPop,
FloatOutOfRange,
// syntax parser
@@ 85,6 87,8 @@ enum class AssemblyErrorCodes
SectionMappingExpectsStringLiteral,
SectionMappingHasDoubleSources,
ModulesNotAllowedInMacros,
+ IncludeNotAllowedInMacros,
+ IncludeNotAllowedInSubroutine,
// assembler
OperatorNotSupportingType = 3000,
@@ 197,6 201,8 @@ enum class AssemblyErrorCodes
TooManyArguments,
ArgumentsPassedToSubroutineCall,
UseOfPseudoInstructionInStandardMode,
+ ExpectedPathString,
+ RecursiveIncludes,
// assembler warnings
SubroutineFallthrough = 3500,
M jasm/io/data_reader.cpp +52 -25
@@ 22,8 22,12 @@ DataReader::~DataReader()
{
#if defined(USE_THREADS)
// wait for threaded operations
- for(auto &file : _files)
- file.file_request.wait();
+ for(auto &file : _files) {
+ try {
+ file.file_request.wait();
+ } catch (Exception &) {
+ }
+ }
#endif
}
@@ 58,43 62,64 @@ uint64_t DataReader::queue_load(const st
}
-size_t DataReader::size(uint64_t handle)
+bool DataReader::size(uint64_t handle, size_t &size, std::string &error)
{
HandleMap::const_iterator it = _handle_map.find(handle);
assert(it != _handle_map.end());
FileInfo &info = *it->second;
- #if defined(USE_THREADS)
- if (info.size_future.valid())
- info.size = info.size_future.get();
- #else
- if (!info.loaded) {
- load(&info);
+ if (!info.size_obtained) {
+ #if defined(USE_THREADS)
+ if (info.size_future.valid()) {
+ try {
+ info.size = info.size_future.get();
+ } catch (Exception &e) {
+ info.file_size_error = e.message;
+ }
+ }
+ #else
+ try {
+ load(&info);
+ } catch (Exception &e) {
+ info.file_size_error = e.message;
+ info.file_data_error = e.message;
+ }
info.loaded = true;
- }
- #endif
+ #endif
+ info.size_obtained = true;
+ }
- return info.size;
+ size = info.size;
+ error = info.file_size_error;
+ return info.file_size_error.empty();
}
-const std::vector<uint8_t> &DataReader::data(uint64_t handle)
+bool DataReader::data(uint64_t handle, const std::vector<uint8_t> *&data_ptr, std::string &error)
{
HandleMap::const_iterator it = _handle_map.find(handle);
assert(it != _handle_map.end());
FileInfo &info = *it->second;
- #if defined(USE_THREADS)
- if (info.data_future.valid())
- return info.data_future.get();
- #else
- if (!info.loaded) {
- load(&info);
- info.loaded = true;
+ if (!info.loaded) {
+ try {
+ #if defined(USE_THREADS)
+ if (info.data_future.valid()) {
+ info.data_future.get();
+ }
+ #else
+ load(&info);
+ #endif
+ } catch (Exception &e) {
+ info.file_data_error = e.message;
}
- #endif
+ info.loaded = true;
+ }
- return info.data;
+ bool success = info.file_data_error.empty();
+ data_ptr = success ? &info.data : nullptr;
+ error = info.file_data_error;
+ return success;
}
@@ 122,8 147,9 @@ void DataReader::load(FileInfo *file_inf
#else
#error "Platform not supported"
#endif
- if (!file.is_open())
+ if (!file.is_open()) {
throw FileException("Failed to open " + file_path);
+ }
// get file size
size = static_cast<size_t>(file.tellg());
@@ 152,12 178,13 @@ void DataReader::load(FileInfo *file_inf
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char *>(file_info->data.data()), static_cast<int64_t>(size));
- if (!file.good())
+ if (!file.good()) {
throw FileException("Failed to read " + file_path);
+ }
#if defined(USE_THREADS)
// notify the caller of the data
- file_info->data_promise.set_value(file_info->data);
+ file_info->data_promise.set_value(&file_info->data);
#endif
#if defined(USE_THREADS)
M jasm/io/data_reader.h +20 -10
@@ 29,14 29,16 @@ public:
uint64_t queue_load(const std::string_view &filename);
/// This will block until the size has been determined and then return it or a fail state.
- /// @throw An exception is thrown if a file error occurred.
- /// @return The size of the file in bytes.
- size_t size(uint64_t handle);
+ /// @param size Will be set to the size of the file in bytes, if successful.
+ /// @param error Will be set to an error message, if not successful.
+ /// @return True if successful.
+ bool size(uint64_t handle, size_t &size, std::string &error);
/// This will block until the data has been loaded and then return it or a fail state.
- /// @throw An exception is thrown if a file error occurred.
- /// @return A pointer to the data.
- const std::vector<uint8_t> &data(uint64_t handle);
+ /// @param data_ptr Will be set to a pointer to the data, if successful.
+ /// @param error Will be set to an error message, if not successful.
+ /// @return True if successful.
+ bool data(uint64_t handle, const std::vector<uint8_t> *&data_ptr, std::string &error);
private:
struct FileInfo;
@@ 49,17 51,25 @@ private:
const std::vector<std::string> _include_dirs;
struct FileInfo {
+ FileInfo()
+ : size_obtained(false)
+ , loaded(false)
+ , size(0)
+ {}
+
std::string filename;
+ std::string file_size_error; ///< Non-empty if an error occurred while obtaining the size.
+ std::string file_data_error; ///< Non-empty if an error occurred while loading.
#if defined(USE_THREADS)
std::promise<size_t> size_promise;
std::future<size_t> size_future;
- std::promise<std::vector<uint8_t> &> data_promise;
- std::future<std::vector<uint8_t> &> data_future;
+ std::promise<std::vector<uint8_t> *> data_promise;
+ std::future<std::vector<uint8_t> *> data_future;
std::future<void> file_request;
- #else
- bool loaded;
#endif
+ bool size_obtained;
+ bool loaded;
size_t size; ///< Cached size for when future has been consumed.
std::vector<uint8_t> data;
A => jasm/io/hex_source_writer.cpp +195 -0
@@ 0,0 1,195 @@
+#include "pch.h"
+