Reserve multidimentional arrays.
M jasm/assemble/assembler_impl/assembler_impl.cpp +7 -1
@@ 434,7 434,13 @@ uint64_t Assembler::add_array_type_descr
 	fill_type_array_operators(type);
 	fill_type_integer_operators(type);
 
-	return add_type_to_type_map(type, p);
+	uint64_t hash = type.hash(0);
+	if (_current_pass.type_lookup.has(hash)) {
+		_current_pass.types.rewind(p);
+	} else {
+		hash = add_type_to_type_map(type, p);
+	}
+	return hash;
 }
 
 void Assembler::setup_predefined_constants(bool generate)

          
M jasm/assemble/assembler_impl/assembler_impl.h +4 -0
@@ 941,6 941,10 @@ private:
 	/// @param array_length Only valid if @a type_reference refers to an array.
 	bool fill_offset_value(bool generate, const TypeReference &type_reference, int32_t array_length, const SourceLocation &source_location, Value &value);
 
+	/// Generate a type from a type reference structure. This can reuse an existing type or create new ones.
+	/// @return True if successful.
+	bool generate_offset_type(bool generate, const TypeReference &type_reference, const std::vector<int32_t> array_lengths, const SourceLocation &source_location, ValueType &type, uint64_t &type_hash);
+
 	/// Create a new value for a symbol and if the name is unique it will also be mapped to the value.
 	/// Non unique values will be dangling but that is not a problem really. It simplifies the code that
 	/// can parse as if the value was going to be filled. Get a reference to the new value using _current_pass_values.back().

          
M jasm/assemble/assembler_impl/syntax_impl.cpp +61 -13
@@ 582,18 582,19 @@ bool Assembler::resolve_type_hash(bool g
 			// set underlying_type_hash to it
 			assert(false);
 			throw Exception("not implemented");
-		} else {
-			type_hash = symbol_value.type_hash;
 		}
 
+		type_hash = symbol_value.type_hash;
 		return true;
 	}
 
 	UNREACHABLE_CODE(throw Exception("internal error"));
 }
 
+// TODO: REMOVE
 bool Assembler::fill_offset_value(bool generate, const TypeReference &type_reference, int32_t array_length, const SourceLocation &source_location, Value &value)
 {
+	/*
 	uint64_t underlying_type_hash;
 	if (!resolve_type_hash(generate, type_reference, source_location, underlying_type_hash))
 		return false;

          
@@ 617,6 618,36 @@ bool Assembler::fill_offset_value(bool g
 	}
 
 	return true;
+	*/
+	assert(false);
+	return false;
+}
+
+bool Assembler::generate_offset_type(bool generate, const TypeReference &type_reference, const std::vector<int32_t> array_lengths, const SourceLocation &source_location, ValueType &type, uint64_t &type_hash)
+{
+	uint64_t underlying_type_hash;
+	if (!resolve_type_hash(generate, type_reference, source_location, underlying_type_hash))
+		return false;
+
+	// map typenames to offset types for typenames in symbols
+	underlying_type_hash = map_fundamental_types_to_offset_types(underlying_type_hash);
+
+	if (!type_reference.is_array) {
+		const TypeDescription &type_desc = find_type(underlying_type_hash);
+		type = type_desc.type;
+		type_hash = underlying_type_hash;
+		return true;
+	}
+
+	// it's an array, so loop and generate types as deep as the arrays go
+	type = ValueType::ArrayOffset;
+	type_hash = underlying_type_hash;
+	for(uint32_t depth = 0; depth < type_reference.array_depth; ++depth)
+	{
+		int32_t array_length = array_lengths[depth];
+		type_hash = add_array_type_description(type_hash, array_length);
+	}
+	return true;
 }
 
 const SyntaxToken *Assembler::parse_array_length(bool generate, bool &success, const SyntaxToken *t, int32_t min_length, int32_t &array_length)

          
@@ 683,23 714,37 @@ const SyntaxToken *Assembler::parse_rese
 	if (export_enabled)
 		value.set_is_public(true);
 
-	// determine array length, if applicable
-	int32_t array_length = 1;
-	if (reserve.type_reference.array_flag == ArrayFlag::ArrayWithSize) {
-		bool success;
-		t = parse_array_length(generate, success, t, 0, array_length);
-		if (!success) {
-			set_unknown(value);
-			return t;
+	// parse all array sizes
+	std::vector<int32_t> array_sizes;
+	bool size_parse_success = true;
+	if (reserve.type_reference.is_array) {
+		array_sizes.resize(reserve.type_reference.array_depth);
+		for(uint32_t index = 0; index < reserve.type_reference.array_depth; ++index) {
+			ArrayFlag flag = reserve.type_reference.array_flags[index];
+			assert(flag == ArrayFlag::ArrayWithSize); // all reserved sizes should be known
+			if (flag == ArrayFlag::ArrayWithSize) {
+				bool success = false;
+				int32_t array_length = 0;
+				t = parse_array_length(generate, success, t, 0, array_length);
+				if (success) {
+					array_sizes[index] = array_length;
+				} else {
+					size_parse_success = false;
+				}
+			}
 		}
 	}
 
+	if (!size_parse_success) {
+		set_unknown(value);
+		return t;
+	}
+
 	// set the value base and offset (this will enable use of offsetof() and further offsetting
 	value.offset_base = _program_counter.integer_value;
 	value.offset = 0;
 
-	assert(reserve.type_reference.array_flag != ArrayFlag::ArrayWithUnknownSize); // this is not allowed in reservations
-	if (!fill_offset_value(generate, reserve.type_reference, array_length, reserve.source_location, value)) {
+	if (!generate_offset_type(generate, reserve.type_reference, array_sizes, reserve.source_location, value.type, value.type_hash)) {
 		set_unknown(value);
 		return t;
 	}

          
@@ 1101,6 1146,8 @@ const SyntaxToken *Assembler::parse_defi
 	// Groups are used for arrays and structs. A string is a form of multi byte entry
 	// so that will require a group.
 
+	assert(false); // TODO: Fix
+/*
 	// parse the size of the array if that is specified
 	int32_t array_length = 0;
 	if (define.type_reference.array_flag == ArrayFlag::ArrayWithSize) {

          
@@ 1159,7 1206,8 @@ const SyntaxToken *Assembler::parse_defi
 		constexpr bool increase = true;
 		_hex_source_writer->write_data(static_cast<uint32_t>(program_counter_before), &data[data_size_before], increase, generated_size, define.source_location.file_index, define.source_location.row, end_line);
 	}
-	
+	*/
+
 	t = skip_to_end_of_define(t);
 	return t;
 }

          
M jasm/exceptions/error_codes.h +1 -0
@@ 89,6 89,7 @@ enum class AssemblyErrorCodes
 	ModulesNotAllowedInMacros,
 	IncludeNotAllowedInMacros,
 	IncludeNotAllowedInSubroutine,
+	TooManyNestedArrays,
 
 	// assembler
 	OperatorNotSupportingType = 3000,

          
M jasm/syntax/syntax_parser.cpp +19 -9
@@ 1544,29 1544,39 @@ const Token *SyntaxParser::parse_type_re
 	}
 
 	// check if the type is an array
-	t = skip_whitespaces(t);
-	if (is_operator(t, OperatorType::LeftBracket)) {
+	reference.array_depth = 0;
+	while(true) {
+		t = skip_whitespaces(t);
+		if (!is_operator(t, OperatorType::LeftBracket)) {
+			break;
+		}
+
+		if (reference.array_depth == max_array_depth) {
+			std::string msg("Too many nested arrays");
+			throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::TooManyNestedArrays, msg);
+		}
+
 		// We have an array so we must parse the size of the array now.
 		// Some type references don't need a size so we check for empty bracket contents as well.
 		t = skip_whitespaces(consume_next_token());
 		if (is_operator(t, OperatorType::RightBracket)) {
 			if (array_size_is_required) {
-				std::stringstream ss;
-				ss << "Array declaration must have a known size";
-				throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::ArrayDeclarationMustHaveSize, ss.str());
+				std::string msg("Array declaration must have a known size");
+				throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::ArrayDeclarationMustHaveSize, msg);
 			}
-			reference.array_flag = ArrayFlag::ArrayWithUnknownSize;
+			reference.array_flags[reference.array_depth] = ArrayFlag::ArrayWithUnknownSize;
 			t = consume_next_token();
 		} else {
-			reference.array_flag = ArrayFlag::ArrayWithSize;
+			reference.array_flags[reference.array_depth] = ArrayFlag::ArrayWithSize;
 			constexpr bool end_at_unmatched_parenthesis = true;
 			constexpr bool end_at_newline = false;
 			t = parse_and_output_expression(t, end_at_unmatched_parenthesis, end_at_newline);
 			t = parse_operator(t, OperatorType::RightBracket);
 		}
-	} else {
-		reference.array_flag = ArrayFlag::Single;
+
+		++reference.array_depth;
 	}
+	reference.is_array = reference.array_depth > 0;
 	return t;
 }
 

          
M jasm/syntax/syntax_tokens.h +8 -3
@@ 444,7 444,6 @@ enum class DataType : uint8_t
 
 enum ArrayFlag : uint8_t
 {
-	Single,
 	ArrayWithSize,
 	ArrayWithUnknownSize,
 };

          
@@ 452,13 451,19 @@ enum ArrayFlag : uint8_t
 struct TypeReference
 {
 	DataType type; ///< Type of the instance.
-	ArrayFlag array_flag; ///< Tells if the instance is an array of @a type.
+	bool is_array; ///< Tells if the instance is an array of @a type.
 
 	bool global; ///< Used with DataType::Symbol. True if the symbol name is a global name.
-	uint8_t padding;
+	uint8_t array_depth; ///< Used when @a is_array is true.
+	// 8 byte aligned
+
+	static_assert((max_array_depth & 7) == 0, "max depth must be multiple of 8");
+	ArrayFlag array_flags[max_array_depth]; ///< The array size known/unknown state for each array, starting with the innermost.
+	// 8 byte aligned
 
 	uint64_t type_name; ///< Used with DataType::Symbol. Symbol for the type.
 	// 8 byte aligned
+
 	uint64_t type_namespace_handle; ///< Used with DataType::Symbol. Handle to namespaces for the type.
 	// 8 byte aligned
 };

          
M jasm/tokenize/types.h +2 -0
@@ 13,6 13,8 @@ enum class TypenameType : uint8_t
 	NumTypes,
 };
 
+constexpr uint8_t max_array_depth = 16;
+
 std::string_view to_string(TypenameType type);
 
 /// @}

          
A => jasm/unit_tests/results/test_reserve_multiarray_and_access.bin +0 -0

        
A => jasm/unit_tests/results/test_reserve_multiarray_must_have_size.stdout +1 -0
@@ 0,0 1,1 @@ 
+unit_tests/test_reserve_multiarray_must_have_size.asm(10,18) : Error 2020 : Array declaration must have a known size

          
A => jasm/unit_tests/results/test_reserve_multiarray_offsetof.bin +0 -0

        
A => jasm/unit_tests/results/test_reserve_multiarray_too_deep.stdout +1 -0
@@ 0,0 1,1 @@ 
+unit_tests/test_reserve_multiarray_too_deep.asm(10,69) : Error 2056 : Too many nested arrays

          
A => jasm/unit_tests/test_reserve_multiarray_and_access.asm +21 -0
@@ 0,0 1,21 @@ 
+// assembler command line arguments: 6502 [-v0]
+processor "6502"
+
+section code, "main", $8000
+{
+	lda b[0]
+	lda b[1]
+	lda b[0][0]
+	lda b[0][1]
+	lda b[0][2]
+	lda b[0][3]
+	lda b[1][0]
+	lda b[1][1]
+	lda b[1][2]
+	lda b[1][3]
+}
+
+section bss, "variables", $1000
+{
+	reserve byte[4][2] b
+}
  No newline at end of file

          
A => jasm/unit_tests/test_reserve_multiarray_must_have_size.asm +11 -0
@@ 0,0 1,11 @@ 
+// assembler command line arguments: 6502 [-v0]
+processor "6502"
+
+section code, "main", $8000
+{
+}
+
+section bss, "variables", $1000
+{
+	reserve byte[4][] b
+}
  No newline at end of file

          
A => jasm/unit_tests/test_reserve_multiarray_offsetof.asm +17 -0
@@ 0,0 1,17 @@ 
+// assembler command line arguments: 6502 [-v0]
+processor "6502"
+
+section code, "main", $8000
+{
+	lda #offsetof(b[0])
+	lda #offsetof(b[1])
+	lda #offsetof(b[0][0])
+	lda #offsetof(b[0][2])
+	lda #offsetof(b[1][0])
+	lda #offsetof(b[1][2])
+}
+
+section bss, "variables", $1000
+{
+	reserve byte[4][2] b
+}
  No newline at end of file

          
A => jasm/unit_tests/test_reserve_multiarray_too_deep.asm +11 -0
@@ 0,0 1,11 @@ 
+// assembler command line arguments: 6502 [-v0]
+processor "6502"
+
+section code, "main", $8000
+{
+}
+
+section bss, "variables", $1000
+{
+	reserve byte[1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17] b
+}
  No newline at end of file

          
M jasm/utility/token_chain.h +8 -1
@@ 48,13 48,20 @@ public:
 	/// Calculate a hash to compare token chains with each other.
 	uint64_t hash() const;
 
-	TokenReadPosition position() const {
+	TokenReadPosition position() const
+	{
 		TokenReadPosition p;
 		p.u.parts.buffer_index = static_cast<uint32_t>(_buffers.size()) - 1;
 		p.u.parts.buffer_position = static_cast<uint32_t>(_buffers.back().size());
 		return p;
 	}
 
+	void rewind(const TokenReadPosition &position)
+	{
+		_buffers.resize(position.u.parts.buffer_index + 1);
+		_buffers[position.u.parts.buffer_index].resize(position.u.parts.buffer_position);
+	}
+
 private:
 	static constexpr int _alignment = 4;