Merge with develop
154 files changed, 3535 insertions(+), 284 deletions(-)

M core/CMakeLists.txt
M core/core/io/text_reader.cpp
A => core/core/json/json_parser.cpp
A => core/core/json/json_parser.h
A => core/core/json/json_reader.cpp
A => core/core/json/json_reader.h
A => core/core/json/json_tokenizer.cpp
A => core/core/json/json_tokenizer.h
A => core/core/json/json_type.cpp
A => core/core/json/json_type.h
A => core/core/json/json_type_helper.cpp
A => core/core/json/json_type_helper.h
A => core/core/json/line_counter.cpp
A => core/core/json/line_counter.h
M core/pch.h
M hasher/CMakeLists.txt
M jasm/CMakeLists.txt
M jasm/assemble/assembler_impl/assembler_impl.cpp
M jasm/assemble/assembler_impl/assembler_impl.h
M jasm/assemble/assembler_impl/functions_impl.cpp
M jasm/assemble/assembler_impl/operators_impl.cpp
M jasm/assemble/assembler_impl/syntax_impl.cpp
M jasm/assemble/functions.cpp
M jasm/assemble/functions.h
M jasm/assemble/value.h
M jasm/docs/jasm.md
M jasm/exceptions/error_codes.h
M jasm/io/data_reader.cpp
M jasm/io/data_reader.h
M jasm/pch.h
M jasm/processor/45gs02/instructions_45gs02.cpp
M jasm/processor/6502/instructions_6502.cpp
M jasm/processor/65c02/instructions_65c02.cpp
M jasm/processor/65ce02/instructions_65ce02.cpp
M jasm/processor/processor.cpp
M jasm/strings/string_conversions.cpp
M jasm/strings/string_conversions.h
M jasm/syntax/syntax_parser.cpp
M jasm/syntax/syntax_parser.h
R jasm/syntax/syntax_state.cpp => 
M jasm/syntax/syntax_state.h
M jasm/syntax/syntax_tokens.h
M jasm/tokenize/keywords.cpp
M jasm/tokenize/keywords.h
M jasm/tokenize/operators.cpp
M jasm/tokenize/operators.h
M jasm/tokenize/position_tracker.cpp
M jasm/tokenize/position_tracker.h
M jasm/tokenize/tokenizer.cpp
A => jasm/unit_tests/binary_file_with_zeroes.bin
A => jasm/unit_tests/broken.json
A => jasm/unit_tests/file_with_break.jasm
M jasm/unit_tests/results/test_all_instructions_45gs02.bin
M jasm/unit_tests/results/test_all_instructions_6502.bin
A => jasm/unit_tests/results/test_break_in_loop_in_loop.bin
A => jasm/unit_tests/results/test_break_in_macro_inside_loop_1.bin
A => jasm/unit_tests/results/test_break_in_macro_inside_loop_2.bin
A => jasm/unit_tests/results/test_break_in_macro_inside_loop_3.bin
A => jasm/unit_tests/results/test_break_in_macro_inside_loop_4.bin
A => jasm/unit_tests/results/test_break_in_macro_outside_loop.stdout
A => jasm/unit_tests/results/test_break_in_unused_macro.bin
A => jasm/unit_tests/results/test_break_inside_module.stdout
A => jasm/unit_tests/results/test_break_inside_namespace.bin
A => jasm/unit_tests/results/test_break_inside_section.stdout
A => jasm/unit_tests/results/test_break_inside_section_mapping.bin
A => jasm/unit_tests/results/test_break_inside_section_part.stdout
A => jasm/unit_tests/results/test_break_inside_subroutine.stdout
A => jasm/unit_tests/results/test_break_without_for_in_separate_file.stdout
A => jasm/unit_tests/results/test_eof_in_character_literal.stdout
A => jasm/unit_tests/results/test_eof_start_of_binary_literal.stdout
A => jasm/unit_tests/results/test_eof_start_of_character_literal.stdout
A => jasm/unit_tests/results/test_eof_start_of_hex_literal.stdout
A => jasm/unit_tests/results/test_for_loop_break.bin
A => jasm/unit_tests/results/test_function_max_on_list.bin
A => jasm/unit_tests/results/test_function_max_on_list_with_multiple_arguments.stdout
A => jasm/unit_tests/results/test_function_max_with_empty_list.stdout
A => jasm/unit_tests/results/test_function_max_with_non_numeric.stdout
A => jasm/unit_tests/results/test_function_min_on_list.bin
A => jasm/unit_tests/results/test_json_import.bin
A => jasm/unit_tests/results/test_json_import_missing_file.stdout
A => jasm/unit_tests/results/test_json_import_parse_failure.stdout
A => jasm/unit_tests/results/test_json_import_with_numeric_argument.stdout
A => jasm/unit_tests/results/test_operator_assignment_add_on_dynamic_string.bin
A => jasm/unit_tests/results/test_operator_assignment_add_on_static_string.bin
R jasm/unit_tests/results/test_operator_left_shift.bin => 
R jasm/unit_tests/results/test_operator_right_shift.bin => 
A => jasm/unit_tests/results/test_operator_shift_arithmetic_right.bin
A => jasm/unit_tests/results/test_operator_shift_arithmetic_right_negative.bin
A => jasm/unit_tests/results/test_operator_shift_logical_left.bin
A => jasm/unit_tests/results/test_operator_shift_logical_left_negative.bin
A => jasm/unit_tests/results/test_operator_shift_logical_right.bin
A => jasm/unit_tests/results/test_operator_shift_logical_right_negative.bin
A => jasm/unit_tests/results/test_range_for_over_dynamic_string.bin
A => jasm/unit_tests/results/test_range_for_over_list_break.bin
A => jasm/unit_tests/results/test_range_for_over_static_string.bin
A => jasm/unit_tests/results/test_range_for_over_string_break.bin
A => jasm/unit_tests/results/test_repeat_loop_break.bin
M jasm/unit_tests/results/test_string_conversion_with_format_and_subformat_property.bin
M jasm/unit_tests/results/test_string_conversion_with_invalid_format_property.stdout
A => jasm/unit_tests/results/test_tokenize_without_end_character.stdout
A => jasm/unit_tests/test.json
M jasm/unit_tests/test_all_instructions_45gs02.asm
M jasm/unit_tests/test_all_instructions_6502.asm
A => jasm/unit_tests/test_break_in_loop_in_loop.asm
A => jasm/unit_tests/test_break_in_macro_inside_loop_1.asm
A => jasm/unit_tests/test_break_in_macro_inside_loop_2.asm
A => jasm/unit_tests/test_break_in_macro_inside_loop_3.asm
A => jasm/unit_tests/test_break_in_macro_inside_loop_4.asm
A => jasm/unit_tests/test_break_in_macro_outside_loop.asm
A => jasm/unit_tests/test_break_in_unused_macro.asm
A => jasm/unit_tests/test_break_inside_module.asm
A => jasm/unit_tests/test_break_inside_namespace.asm
A => jasm/unit_tests/test_break_inside_section.asm
A => jasm/unit_tests/test_break_inside_section_mapping.asm
A => jasm/unit_tests/test_break_inside_section_part.asm
A => jasm/unit_tests/test_break_inside_subroutine.asm
A => jasm/unit_tests/test_break_without_for_in_separate_file.asm
A => jasm/unit_tests/test_eof_in_character_literal.asm
A => jasm/unit_tests/test_eof_start_of_binary_literal.asm
A => jasm/unit_tests/test_eof_start_of_character_literal.asm
A => jasm/unit_tests/test_eof_start_of_hex_literal.asm
A => jasm/unit_tests/test_for_loop_break.asm
A => jasm/unit_tests/test_function_max_on_list.asm
A => jasm/unit_tests/test_function_max_on_list_with_multiple_arguments.asm
A => jasm/unit_tests/test_function_max_with_empty_list.asm
A => jasm/unit_tests/test_function_max_with_non_numeric.asm
A => jasm/unit_tests/test_function_min_on_list.asm
A => jasm/unit_tests/test_json_import.asm
A => jasm/unit_tests/test_json_import_missing_file.asm
A => jasm/unit_tests/test_json_import_parse_failure.asm
A => jasm/unit_tests/test_json_import_with_numeric_argument.asm
A => jasm/unit_tests/test_operator_assignment_add_on_dynamic_string.asm
A => jasm/unit_tests/test_operator_assignment_add_on_static_string.asm
M jasm/unit_tests/test_operator_assignment_right_shift.asm
R jasm/unit_tests/test_operator_left_shift.asm => 
R jasm/unit_tests/test_operator_right_shift.asm => 
A => jasm/unit_tests/test_operator_shift_arithmetic_right.asm
A => jasm/unit_tests/test_operator_shift_arithmetic_right_negative.asm
A => jasm/unit_tests/test_operator_shift_logical_left.asm
A => jasm/unit_tests/test_operator_shift_logical_left_negative.asm
A => jasm/unit_tests/test_operator_shift_logical_right.asm
A => jasm/unit_tests/test_operator_shift_logical_right_negative.asm
A => jasm/unit_tests/test_range_for_over_dynamic_string.asm
A => jasm/unit_tests/test_range_for_over_list_break.asm
A => jasm/unit_tests/test_range_for_over_static_string.asm
A => jasm/unit_tests/test_range_for_over_string_break.asm
A => jasm/unit_tests/test_repeat_loop_break.asm
M jasm/unit_tests/test_string_conversion_with_format_and_subformat_property.asm
M jasm/unit_tests/test_string_conversion_with_format_and_subformat_property_2.asm
M jasm/unit_tests/test_string_conversion_with_format_subformat_and_locale_property.asm
M jasm/unit_tests/test_string_conversion_with_invalid_format_property.asm
A => jasm/unit_tests/test_tokenize_without_end_character.asm
M jasm/website/site/docs/index.html
M jasm/website/site/index.html
M core/CMakeLists.txt +1 -2
@@ 22,7 22,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_F
 
 add_library(core STATIC ${core_src})
 
-set_property(TARGET core PROPERTY CXX_STANDARD 17)
-set_property(TARGET core PROPERTY CXX_STANDARD_REQUIRED ON)
+set_property(TARGET core PROPERTY CXX_STANDARD 20)
 
 target_include_directories(core PUBLIC "${CMAKE_CURRENT_LIST_DIR}")

          
M core/core/io/text_reader.cpp +5 -5
@@ 5,7 5,7 @@ 
 #include <core/strings/utf8.h>
 #include <fstream>
 #include <locale>
-#include <memory>
+#include <vector>
 
 namespace core
 {

          
@@ 33,15 33,15 @@ std::string load_file(const std::string 
 
 	// get file size
 	uint64_t size = static_cast<uint64_t>(file.tellg());
-	auto data = std::unique_ptr<char[]>(new char[size + 1]); // add one to null terminate
+	std::vector<char> data;
+	data.resize(size);
 
 	// read contents
 	file.seekg(0, std::ios::beg);
-	file.read(data.get(), static_cast<std::streamsize>(size));
-	data[static_cast<size_t>(size)] = 0; // null terminate the data to convert to string
+	file.read(data.data(), static_cast<std::streamsize>(size));
 	file.close();
 
-	return std::string(data.get());
+	return std::string(data.data(), data.size());
 }
 
 } // namespace core

          
A => core/core/json/json_parser.cpp +194 -0
@@ 0,0 1,194 @@ 
+#include "pch.h"
+
+#include <core/json/json_parser.h>
+#include <core/json/json_type.h>
+
+namespace core { namespace json {
+
+	Parser::Parser(std::wistream &in)
+		: _tokenizer(in)
+		, _reader(in)
+	{
+	}
+
+	std::unique_ptr<JsonType> Parser::parse() {
+		std::unique_ptr<JsonType> value = std::make_unique<JsonType>();
+		parse(*value);
+
+		if (parse_error()) {
+			return nullptr;
+		}
+
+		return value;
+	}
+
+	void Parser::parse(JsonType &object) {
+		object.clear();
+		_reader.reset();
+		skip_whitespace();
+		parse_object(object);
+	}
+
+	bool Parser::parse_error() const {
+		return _reader.parse_error();
+	}
+
+	std::string Parser::parse_error_string() {
+		return _reader.parse_error_string();
+	}
+
+	void Parser::skip_whitespace() {
+		_reader.skip_whitespace();
+	}
+
+	void Parser::parse_object(JsonType &value) {
+		Token token = _tokenizer.next_token();
+		if (token == Token::ObjectStart)
+			parse_object_with_brackets(value);
+		else
+			parse_object_without_brackets(value);
+	}
+
+	void Parser::parse_object_with_brackets(JsonType &value) {
+		_reader.parse_object_start();
+		if (parse_error())
+			return;
+
+		skip_whitespace();
+		parse_object_without_brackets(value);
+		if (parse_error())
+			return;
+
+		_reader.parse_object_end();
+	}
+
+	void Parser::parse_object_without_brackets(JsonType &value) {
+		value._type_id = TypeID::Object;
+		value._data.o = new JsonType::MapType;
+		while(true) {
+			Token token = _tokenizer.next_token();
+			if (token == Token::EndOfFile || token == Token::ObjectEnd)
+				break;
+
+			std::wstring key = _reader.parse_string();
+			if (value.has(key.c_str())) {
+				_reader.generate_parse_error("Duplicated object keys.");
+				break;
+			}
+			parse_colon_with_whitespace();
+			if (_reader.parse_error())
+				break;
+			
+			JsonType &obj = (*value._data.o)[key];
+			parse_value(obj);
+			if (_reader.parse_error())
+				break;
+
+			parse_whitespace_or_comma();
+		}
+	}
+	
+	void Parser::parse_colon_with_whitespace() {
+		skip_whitespace();
+		_reader.parse_colon();
+		skip_whitespace();
+	}
+
+	void Parser::parse_whitespace_or_comma() {
+		skip_whitespace();
+		Token token = _tokenizer.next_token();
+		if (token == Token::Comma) {
+			_reader.parse_comma();
+			skip_whitespace();
+		}
+	}
+
+	void Parser::parse_value(JsonType &value) {
+		Token token = _tokenizer.next_token();
+		switch(token) {
+		case Token::Null:
+			_reader.parse_null();
+			value._type_id = TypeID::None;
+			break;
+		case Token::True:
+			_reader.parse_true();
+			value._type_id = TypeID::Bool;
+			value._data.b = true;
+			break;
+		case Token::False:
+			_reader.parse_false();
+			value._type_id = TypeID::Bool;
+			value._data.b = false;
+			break;
+		case Token::Quote:
+		case Token::Character:
+			value._type_id = TypeID::String;
+			value._data.s = new std::wstring(_reader.parse_string());
+			break;
+		case Token::Number:
+			parse_number(value);
+			break;
+		case Token::ArrayStart:
+			parse_array_with_brackets(value);
+			break;
+		case Token::ObjectStart:
+			parse_object_with_brackets(value);
+			break;
+		case Token::EndOfFile:
+		case Token::Whitespace:
+		case Token::ObjectEnd:
+		case Token::ArrayEnd:
+		case Token::Comma:
+		case Token::Colon:
+			_reader.generate_parse_error("Expected value.");
+		}
+	}
+
+	void Parser::parse_number(JsonType &value) {
+		bool is_integer = false;
+		int i;
+		float f;
+		_reader.parse_number(f, i, is_integer);
+		value._type_id = TypeID::Number;
+		if (is_integer) {
+			value._data.i = i;
+			value._number_type = NumberType::Integer;
+		} else {
+			value._data.f = f;
+			value._number_type = NumberType::Float;
+		}
+	}
+
+	void Parser::parse_array_with_brackets(JsonType &value) {
+		_reader.parse_array_start();
+		if (parse_error())
+			return;
+
+		skip_whitespace();
+		parse_array_without_brackets(value);
+		if (parse_error())
+			return;
+
+		_reader.parse_array_end();
+	}
+
+	void Parser::parse_array_without_brackets(JsonType &value) {
+		value._type_id = TypeID::Array;
+		value._data.a = new JsonType::ArrayType;
+		while(true) {
+			Token token = _tokenizer.next_token();
+			if (token == Token::EndOfFile || token == Token::ArrayEnd)
+				break;
+
+			value._data.a->resize(value._data.a->size() + 1);
+			JsonType &v = value._data.a->back();
+			parse_value(v);
+			if (parse_error())
+				break;
+
+			parse_whitespace_or_comma();
+		}
+	}
+	
+
+}}

          
A => core/core/json/json_parser.h +54 -0
@@ 0,0 1,54 @@ 
+#pragma once
+
+#include <core/json/json_tokenizer.h>
+#include <core/json/json_reader.h>
+#include <memory>
+
+namespace core { namespace json {
+
+	/// \addtogroup json
+	/// \{
+
+	class JsonType;
+
+	/// \class Parser
+	/// \brief This class parses JSON from a file and constructs a JSON object from that.
+	class Parser
+	{
+	public:
+		Parser(std::wistream &file);
+
+		/// Parse and return an object containing the entire json structure.
+		std::unique_ptr<JsonType> parse();
+		/// Parse and modify an object containing the entire json structure.
+		void parse(JsonType &object);
+
+		/// Returns true if the parse process resulted in an error.
+		bool parse_error() const;
+		/// Returns a string describing the parse error, if any.
+		std::string parse_error_string();
+
+	private:
+		void skip_whitespace();
+		/// Parse an object assuming that leading whitespace has been removed.
+		void parse_object(JsonType &obj);
+		/// Parse an object assuming that next token is a curly bracket.
+		void parse_object_with_brackets(JsonType &obj);
+		/// Parse an object without brackets assuming that leading whitespace has been removed.
+		void parse_object_without_brackets(JsonType &obj);
+
+		void parse_colon_with_whitespace();
+		void parse_whitespace_or_comma();
+
+		void parse_value(JsonType &value);
+		void parse_number(JsonType &value);
+		void parse_array_with_brackets(JsonType &value);
+		void parse_array_without_brackets(JsonType &value);
+
+		Tokenizer	_tokenizer;
+		Reader		_reader;
+	};
+
+	/// \}
+
+}}

          
A => core/core/json/json_reader.cpp +363 -0
@@ 0,0 1,363 @@ 
+#include "pch.h"
+
+#include <cmath>
+#include <core/json/json_reader.h>
+#include <core/json/json_type.h>
+#include <core/strings/utf8.h>
+#include <sstream>
+
+namespace core { namespace json {
+
+	Reader::Reader(std::wistream &in)
+		: _in(in)
+		, _error_string(nullptr)
+		, _char(0)
+	{
+		_buffer.reserve(64);
+	}
+
+	void Reader::reset() {
+		_in.seekg(0);
+		_line_counter.reset();
+		_error_string = nullptr;
+	}
+
+	std::wstring Reader::parse_string() {
+		peek_next_char();
+		if (_char == L'"') {
+			return parse_quoted_string();
+		}
+		if (!is_character()) {
+			generate_parse_error("Expected string.");
+			return L"";
+		}
+
+		clear_buffer();
+
+		while(true) {
+			if (end_of_file()) {
+				break;
+			}
+
+			read_next_char();
+			if (is_delimiter()) {
+				unread_char();
+				break;
+			}
+			if (is_string_encode_char()) {
+				read_and_buffer_encoded_char();
+			} else {
+				buffer_character();
+			}
+		}
+
+		return make_string_from_buffer();
+	}
+
+	void Reader::skip_whitespace() {
+		while(true) {
+			if (end_of_file()) {
+				return;
+			}
+
+			read_next_char();
+
+			if (!is_whitespace()) {
+				unread_char();
+				break;
+			}
+		}
+	}
+
+	std::string Reader::parse_error_string() {
+		std::stringstream error;
+		error << _error_string;
+		error << " (line ";
+		error << std::to_string(_line_counter.line());
+		error << ", column ";
+		error << std::to_string(_line_counter.column());
+		error << ")";
+
+		_error_string = nullptr;
+		return error.str();
+	}
+
+	std::wstring Reader::parse_quoted_string() {
+		read_next_char();
+		clear_buffer();
+
+		while(true) {
+			if (end_of_file()) {
+				generate_parse_error("Missing end quote in string.");
+				break;
+			}
+
+			read_next_char();
+			
+			if (_char == L'"') {
+				break;
+			}
+
+			if (is_string_encode_char()) {
+				read_and_buffer_encoded_char();
+			} else {
+				buffer_character();
+			}
+		}
+
+		return make_string_from_buffer();
+	}
+
+	void Reader::read_and_buffer_encoded_char() {
+		read_next_char();
+
+		if (_char == 'u') {
+			read_and_buffer_hex();
+			return;
+		}
+
+		if (_char == '"' || _char == '\\' || _char == '/') {
+			
+		} else if (_char == 'b') {
+			_char = 8;
+		} else if (_char == 'f') {
+			_char = 12;
+		} else if (_char == 'n') {
+			_char = 10;
+		} else if (_char == 'r') {
+			_char = 13;
+		} else if (_char == 't') {
+			_char = 9;
+		} else {
+			generate_parse_error("Invalid character following \\ in a string.");
+			_line_counter.undo();
+		}
+
+		buffer_character();
+	}
+
+	void Reader::read_and_buffer_hex() {
+		uint32_t wide_char = 0;
+		for(uint32_t i = 0; i < 4; ++i) {
+			if (end_of_file()) {
+				generate_parse_error("Unexpected end of file.");
+				return;
+			}
+
+			read_next_char();
+			wide_char <<= 4;
+			wide_char |= char_to_dec();
+
+			if (parse_error()) {
+				_line_counter.undo();
+			}
+		}
+
+		buffer_wide_character(wide_char);
+	}
+
+	void Reader::generate_parse_error(const char *error_text) {
+		if (_error_string == nullptr) {
+			_error_string = error_text;
+		}
+	}
+
+	uint32_t Reader::char_to_dec() {
+		char_to_lower_case();
+		if (_char >= '0' && _char <= '9')
+			return static_cast<uint8_t>(_char) - static_cast<uint8_t>('0');
+		if (_char >= 'a' && _char <= 'f')
+			return 10U + static_cast<uint8_t>(_char) - static_cast<uint8_t>('a');
+		generate_parse_error("Invalid hexadecimal character.");
+		return 0;
+	}
+
+	void Reader::buffer_wide_character(uint32_t wide_char) {
+		static_assert(sizeof(uint32_t) == sizeof(wchar_t), "Wide character is assumed to be 32-bit");
+		_char = wide_char;
+		buffer_character();
+	}
+
+	void Reader::parse_number(float &float_number, int32_t &int_number, bool &is_integer) {
+		bool negative;
+		is_integer = parse_int(int_number, negative);
+		if (is_integer)
+			return;
+
+		parse_decimal(int_number, negative, float_number);
+	}
+
+	bool Reader::parse_int(int32_t &integer, bool &negative) {
+		negative = is_negative_number();
+		int32_t number = 0;
+		bool parse_float = false;
+
+		while(true) {
+			read_next_char();
+			if (is_digit()) {
+				number = number * 10 + (_char - '0');
+			} else if (_char == 'e' || _char == '.') {
+				unread_char();
+				parse_float = true;
+				break;
+			} else if (is_delimiter()) {
+				unread_char();
+				break;
+			} else {
+				generate_parse_error("Invalid number character.");
+				_line_counter.undo();
+				break;
+			}
+
+			if (end_of_file())
+				break;
+		}
+
+		if (negative) {
+			integer = -number;
+		} else {
+			integer = number;
+		}
+
+		return !parse_float;
+	}
+
+	void Reader::parse_decimal(int32_t integer, bool negative, float &number) {
+		read_next_char();
+		if (_char == 'e') {
+			float exponent = 0.0f;
+			parse_exponent(exponent);
+			number = static_cast<float>(integer) * powf(10.0f, exponent);
+			return;
+		}
+
+		float decimals = 0.0f;
+		float multiplier = 0.1f;
+
+		while(true) {
+			read_next_char();
+			if (is_digit()) {
+				decimals += multiplier * (_char - '0');
+			} else if (_char == 'e') {
+				float exponent = 0.0f;
+				parse_exponent(exponent);
+
+				if (negative) {
+					number = (static_cast<float>(integer) - decimals) * powf(10.0f, exponent);
+				} else {
+					number = (static_cast<float>(integer) + decimals) * powf(10.0f, exponent);
+				}
+				return;
+			} else if (is_delimiter()) {
+				unread_char();
+				break;
+			} else {
+				generate_parse_error("Invalid number character.");
+				_line_counter.undo();
+				break;
+			}
+
+			if (end_of_file())
+				break;
+
+			multiplier *= 0.1f;
+		}
+
+		if (negative)
+			number = static_cast<float>(integer) - decimals;
+		else
+			number = static_cast<float>(integer) + decimals;
+	}
+
+	void Reader::parse_exponent(float &number) {
+		int32_t exponent;
+		bool negative;
+		bool looks_like_float = !parse_int(exponent, negative);
+		if (looks_like_float) {
+			read_next_char();
+			generate_parse_error("Invalid number character.");
+			_line_counter.undo();
+			return;
+		}
+		number = static_cast<float>(exponent);
+	}
+
+	bool Reader::is_negative_number() {
+		peek_next_char();
+		if (_char == '-') {
+			read_next_char();
+			return true;
+		}
+		return false;
+	}
+
+	void Reader::parse_object_start() {
+		parse_single_char(L'{', 0, "Expected curly bracket.");
+	}
+	void Reader::parse_object_end() {
+		parse_single_char(L'}', 0, "Expected ending curly bracket.");
+	}
+
+	void Reader::parse_array_start() {
+		parse_single_char(L'[', 0, "Expected bracket.");
+	}
+	void Reader::parse_array_end() {
+		parse_single_char(L']', 0, "Expected ending bracket.");
+	}
+
+	void Reader::parse_colon() {
+		parse_single_char(L':', '=', "Expected colon or equal sign.");
+	}
+
+	void Reader::parse_comma() {
+		parse_single_char(L',', 0, "Expected comma.");
+	}
+
+	void Reader::parse_null() {
+		parse_expected_string(L"null", "Expected null.");
+	}
+
+	void Reader::parse_true() {
+		parse_expected_string(L"true", "Expected true.");
+	}
+	void Reader::parse_false() {
+		parse_expected_string(L"false", "Expected false.");
+	}
+
+	void Reader::parse_single_char(wchar_t c1, wchar_t c2, const char *error_if_missing) {
+		if (end_of_file()) {
+			generate_parse_error(error_if_missing);
+			return;
+		}
+
+		read_next_char();
+		if (_char != c1 && (c2 == 0 || _char != c2)) {
+			generate_parse_error(error_if_missing);
+			_line_counter.undo();
+		}
+	}
+
+	void Reader::parse_expected_string(std::wstring_view str, const char *error_if_missing) {
+		for(size_t i = 0; i < str.size(); ++i) {
+			if (end_of_file()) {
+				generate_parse_error(error_if_missing);
+				return;
+			}
+
+			read_next_char();
+
+			if (_char != str[i]) {
+				generate_parse_error(error_if_missing);
+				_line_counter.undo();
+			}
+		}
+	}
+
+	std::wstring Reader::make_string_from_buffer() {
+		if (_buffer.empty()) {
+			return std::wstring();
+		}
+		return std::wstring(_buffer.data(), _buffer.size());
+	}
+
+}}

          
A => core/core/json/json_reader.h +168 -0
@@ 0,0 1,168 @@ 
+#pragma once
+
+#include <core/json/line_counter.h>
+#include <istream>
+#include <string>
+#include <vector>
+
+namespace core {
+namespace json {
+
+	class JsonType;
+
+	/// \class Reader
+	/// \brief This class handles parsing items in a JSON source file.
+	class Reader
+	{
+	public:
+		Reader(std::wistream &in);
+
+		/// Reset the reader to start parsing a new file. This resets the line and column
+		/// counters.
+		void reset();
+
+		/// Parse an expected string and return it.
+		std::wstring parse_string();
+
+		/// Parse an expected number. The relevant parameter is updated and is_integer
+		/// tells which one is valid.
+		void parse_number(float &float_number, int32_t &int_number, bool &is_integer);
+
+		void parse_object_start();
+		void parse_object_end();
+		void parse_array_start();
+		void parse_array_end();
+		void parse_colon();
+		void parse_comma();
+		void parse_null();
+		void parse_true();
+		void parse_false();
+
+		void skip_whitespace();
+
+		/// Trigger a parse error condition. This makes the reader to reading and
+		/// it will not generate any more errors.
+		void generate_parse_error(const char *error_text);
+
+		/// Returns true if there was an error in the parsing.
+		bool parse_error() const {
+			return _error_string != nullptr;
+		}
+
+		/// Returns the the latest error string and resets the error state.
+		std::string parse_error_string();
+
+		/// Returns true if the file has reached the end.
+		bool end_of_file() const {
+			if (parse_error())
+				return true;
+			return _in.eof();
+		}
+
+	private:
+		std::wstring parse_quoted_string();
+
+		void clear_buffer() {
+			_buffer.clear();
+		}
+
+		void peek_next_char() {
+			read_next_char();
+			unread_char();
+		}
+
+		void read_next_char() {
+			if (parse_error()) {
+				_char = 0;
+				return;
+			}
+
+			_in.get(_char);
+			_line_counter.push(_char);
+		}
+
+		void unread_char() {
+			if (parse_error())
+				return;
+
+			_in.seekg(-1, std::ios_base::cur);
+			_line_counter.undo();
+		}
+
+		void buffer_character() {
+			_buffer.push_back(_char);
+		}
+
+		std::wstring make_string_from_buffer();
+
+		bool is_delimiter() const {
+			return
+				_char == ' ' ||
+				_char == '\t' ||
+				_char == 10 ||
+				_char == 13 ||
+				_char == ',' ||
+				_char == ':' ||
+				_char == ']' ||
+				_char == '}' ||
+				_char == '=';
+		}
+
+		bool is_whitespace() const {
+			return
+				_char == ' ' ||
+				_char == '\t' ||
+				_char == 10 ||
+				_char == 13;
+		}
+
+		bool is_digit() const {
+			return _char >= '0' && _char <= '9';
+		}
+
+		bool is_character() const {
+			return (_char >= 'a' && _char <= 'z') || (_char >= 'A' && _char <= 'Z');
+		}
+
+		bool is_string_encode_char() const {
+			return _char == '\\';
+		}
+
+		void char_to_lower_case()
+		{
+			_char |= 0x20;
+		}
+
+		void read_and_buffer_encoded_char();
+		void read_and_buffer_hex();
+		uint32_t char_to_dec();
+		void buffer_wide_character(uint32_t wide_char);
+		bool is_negative_number();
+
+		/// Parse an integer.
+		/// \param negative Will be set to true if the number starts with a minus sign.
+		/// \return True if the integer was parsed as an integer and false if the number
+		/// looks like a floating point number.
+		bool parse_int(int32_t &integer, bool &negative);
+		/// Parse the decimal part of a floating point number.
+		/// The file reader is expected to have a dot or an 'e' next in
+		/// the stream.
+		/// \param integer The preparsed integer number.
+		/// \param negative True if the number is negative.
+		/// \param number A reference to a number that will be updated with the floating point number.
+		void parse_decimal(int32_t integer, bool negative, float &number);
+
+		void parse_exponent(float &number);
+
+		void parse_single_char(wchar_t c1, wchar_t c2, const char *error_if_missing);
+		void parse_expected_string(std::wstring_view str, const char *error_if_missing);
+
+		std::wistream &_in;
+		LineCounter _line_counter;
+		std::vector<wchar_t> _buffer;
+
+		const char *_error_string;
+		wchar_t _char;
+	};
+
+}}

          
A => core/core/json/json_tokenizer.cpp +79 -0
@@ 0,0 1,79 @@ 
+#include "pch.h"
+
+#include <algorithm>
+#include <core/json/json_tokenizer.h>
+#include <cstring>
+
+namespace core { namespace json {
+
+	Tokenizer::Tokenizer(std::wistream &in)
+		: _in(in)
+	{}
+
+	Token Tokenizer::next_token() {
+		auto pos = _in.tellg();
+		Token token = read_next_token();
+		_in.seekg(pos);
+		return token;
+	}
+	
+	Token Tokenizer::read_next_token() {
+		if (_in.eof()) {
+			return Token::EndOfFile;
+		}
+		
+		wchar_t c;
+		_in.get(c);
+		
+		if (c == 10 || c == 13 || c == ' ' || c == '\t')
+			return Token::Whitespace;
+		if (c == '{')
+			return Token::ObjectStart;
+		if (c == '}')
+			return Token::ObjectEnd;
+		if (c == '[')
+			return Token::ArrayStart;
+		if (c == ']')
+			return Token::ArrayEnd;
+		if (c == '-' || (c >= '0' && c <= '9'))
+			return Token::Number;
+		if (c == ',')
+			return Token::Comma;
+		if (c == ':' || c == '=')
+			return Token::Colon;
+		if (c == '"')
+			return Token::Quote;
+		if (c == 't') {
+			if (rest_is(L"rue") && next_token_is_non_alphanumeric())
+				return Token::True;
+		} else if (c == 'f') {
+			if (rest_is(L"alse") && next_token_is_non_alphanumeric())
+				return Token::False;
+		} else if (c == 'n') {
+			if (rest_is(L"ull") && next_token_is_non_alphanumeric())
+				return Token::Null;
+		}
+		return Token::Character;
+	}
+
+	bool Tokenizer::rest_is(std::wstring_view text) {
+		assert(text.size() <= 10); // Compare is too long
+		wchar_t rest[10];
+		_in.read(rest, std::min<size_t>(text.size(), 10));
+		std::streamsize read_chars = _in.gcount();
+		if (read_chars < text.size())
+			return false;
+		return std::memcmp(text.data(), rest, text.size()*sizeof(wchar_t)) == 0;
+	}
+
+	bool Tokenizer::next_token_is_non_alphanumeric() {
+		Token token = next_token();
+		return
+			token != Token::Character &&
+			token != Token::Number &&
+			token != Token::True &&
+			token != Token::False &&
+			token != Token::Null;
+	}
+
+}}

          
A => core/core/json/json_tokenizer.h +48 -0
@@ 0,0 1,48 @@ 
+#pragma once
+
+#include <string>
+#include <istream>
+
+namespace core {
+namespace json {
+
+	/// \addtogroup json
+	/// \{
+
+	enum class Token {
+		EndOfFile,
+		Whitespace,
+		ObjectStart,
+		ObjectEnd,
+		ArrayStart,
+		ArrayEnd,
+		Quote,
+		Character,
+		Number,
+		True,
+		False,
+		Null,
+		Comma,
+		Colon,
+	};
+
+
+	/// \class Tokenizer
+	/// \brief This class determines the next token in a JSON file.
+	class Tokenizer {
+	public:
+		Tokenizer(std::wistream &in);
+
+		Token next_token();
+
+	private:
+		Token read_next_token();
+		bool rest_is(std::wstring_view text);
+		bool next_token_is_non_alphanumeric();
+
+		std::wistream &_in;
+	};
+
+	/// \}
+
+}}

          
A => core/core/json/json_type.cpp +215 -0
@@ 0,0 1,215 @@ 
+#include "pch.h"
+
+#include <core/json/json_type.h>
+#include <limits>
+
+namespace core { namespace json {
+
+	JsonType::JsonType(JsonType &&other)
+		: _type_id(other._type_id)
+		, _number_type(other._number_type)
+		, _data(other._data)
+	{
+		other._type_id = TypeID::None;
+	}
+	
+	JsonType &JsonType::operator=(JsonType &&other)
+	{
+		// free this
+		clear();
+
+		// move
+		_type_id = other._type_id;
+		_number_type = other._number_type;
+		_data = other._data;
+
+		// invalidate other
+		other._type_id = TypeID::None;
+		
+		return *this;
+	}
+
+	void JsonType::clear()
+	{
+		switch(_type_id) {
+			case TypeID::String:
+				delete _data.s;
+				break;
+			case TypeID::Array:
+				delete _data.a;
+				break;
+			case TypeID::Object:
+				delete _data.o;
+				break;
+			case TypeID::Number:
+			case TypeID::Bool:
+			case TypeID::None:
+				break;
+		}
+		_type_id = TypeID::None;
+	}
+
+	bool JsonType::can_convert_to_uint() const
+	{
+		if (_type_id == TypeID::Number) {
+			if (_number_type == NumberType::Float) {
+				double d = static_cast<double>(_data.f);
+				return d <= static_cast<double>(std::numeric_limits<uint32_t>::max()) && d >= static_cast<double>(std::numeric_limits<uint32_t>::min());
+			} else if (_number_type == NumberType::Integer) {
+				return _data.i >= 0;
+			}
+		}
+		return false;
+	}
+
+	float JsonType::as_float() const
+	{
+		assert(is_number()); // expected number
+		if (_number_type == NumberType::Float)
+			return _data.f;
+		else
+			return static_cast<float>(_data.i);
+	}
+
+	int32_t JsonType::as_int() const
+	{
+		assert(is_number()); // expected number
+		if (_number_type == NumberType::Float)
+			return static_cast<int32_t>(_data.f);
+		else
+			return _data.i;
+	}
+
+	uint32_t JsonType::as_uint() const
+	{
+		assert(is_number()); // expected number
+		if (_number_type == NumberType::Float) {
+			assert(_data.f >= 0.0f); // expected positive integer
+			return static_cast<uint32_t>(_data.f);
+		} else {
+			assert(_data.i >= 0); // expected positive integer
+			return static_cast<uint32_t>(_data.i);
+		}
+	}
+
+	bool JsonType::as_bool() const
+	{
+		assert(is_bool()); // expected bool
+		return _data.b;
+	}
+
+	const std::wstring &JsonType::as_string() const
+	{
+		assert(is_string()); // expected string
+		return *_data.s;
+	}
+
+	// array methods
+
+	JsonType::const_iterator JsonType::begin() const
+	{
+		assert(is_array()); // expected array
+		return _data.a->begin();
+	}
+
+	JsonType::const_iterator JsonType::end() const
+	{
+		assert(is_array()); // expected array
+		return _data.a->end();
+	}
+
+	JsonType::iterator JsonType::begin()
+	{
+		assert(is_array()); // expected array
+		return _data.a->begin();
+	}
+
+	JsonType::iterator JsonType::end()
+	{
+		assert(is_array()); // expected array
+		return _data.a->end();
+	}
+
+	const JsonType &JsonType::operator[](size_t index) const
+	{
+		assert(is_array()); // expected array
+		return (*_data.a)[index];
+	}
+
+	JsonType &JsonType::operator[](size_t index)
+	{
+		assert(is_array()); // expected array
+		return (*_data.a)[index];
+	}
+
+	// map methods
+
+	JsonType::MapType::const_iterator JsonType::map_begin() const
+	{
+		assert(is_object()); // expected object
+		return _data.o->begin();
+	}
+
+	JsonType::MapType::const_iterator JsonType::map_end() const
+	{
+		assert(is_object()); // expected object
+		return _data.o->end();
+	}
+
+	JsonType::MapType::iterator JsonType::map_begin()
+	{
+		assert(is_object()); // expected object
+		return _data.o->begin();
+	}
+
+	JsonType::MapType::iterator JsonType::map_end()
+	{
+		assert(is_object()); // expected object
+		return _data.o->end();
+	}
+
+	const JsonType &JsonType::operator[](const wchar_t *key) const
+	{
+		assert(is_object()); // expected object
+		return (*_data.o)[key];
+	}
+
+	JsonType &JsonType::operator[](const wchar_t *key)
+	{
+		assert(is_object()); // expected object
+		return (*_data.o)[key];
+	}
+
+	bool JsonType::has(std::wstring_view key) const
+	{
+		assert(is_object()); // expected object
+		return (*_data.o).find(std::wstring(key)) != _data.o->end();
+	}
+
+	// array or map methods
+
+	size_t JsonType::size() const
+	{
+		assert(is_object() || is_array()); // expected object or array
+		if (is_object()) {
+			return _data.o->size();
+		} else if (is_array()) {
+			return _data.a->size();
+		} else {
+			return 0;
+		}
+	}
+
+	bool JsonType::empty() const
+	{
+		assert(is_object() || is_array()); // expected object or array
+		if (is_object()) {
+			return _data.o->empty();
+		} else if (is_array()) {
+			return _data.a->empty();
+		} else {
+			return true;
+		}
+	}
+
+}}

          
A => core/core/json/json_type.h +117 -0
@@ 0,0 1,117 @@ 
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace core {
+	namespace json {
+
+	class Parser;
+	class Reader;
+
+	/// \addtogroup json
+	/// \{
+
+	enum class TypeID {
+		Number,
+		String,
+		Array,
+		Object,
+		Bool,
+		None,
+	};
+
+	enum class NumberType {
+		Float,
+		Integer,
+	};
+
+	/// \class JsonType
+	/// \brief This is the class of all JSON types.
+	class JsonType
+	{
+		friend class json::Parser;
+		friend class json::Reader;
+
+	public:
+		using ArrayType = std::vector<JsonType>;
+		using MapType = std::map<std::wstring, JsonType>;
+		using const_iterator = ArrayType::const_iterator;
+		using iterator = ArrayType::iterator;
+
+		JsonType()
+			: _type_id(TypeID::None)
+		{}
+
+		~JsonType() {
+			clear();
+		}
+
+		JsonType(const JsonType &other) = delete;
+		JsonType(JsonType &&other);
+		JsonType &operator=(const JsonType &other) = delete;
+		JsonType &operator=(JsonType &&other);
+		
+		bool is_number() const { return _type_id == TypeID::Number; }
+		bool is_int() const { return _type_id == TypeID::Number && _number_type == NumberType::Integer; }
+		bool is_float() const { return _type_id == TypeID::Number && _number_type == NumberType::Float; }
+		bool is_string() const { return _type_id == TypeID::String; }
+		bool is_array() const { return _type_id == TypeID::Array; }
+		bool is_object() const { return _type_id == TypeID::Object; }
+		bool is_bool() const { return _type_id == TypeID::Bool; }
+		bool is_null() const { return _type_id == TypeID::None; }
+		
+		bool can_convert_to_uint() const;
+
+		float as_float() const;
+		int32_t as_int() const;
+		uint32_t as_uint() const;
+		bool as_bool() const;
+		const std::wstring &as_string() const;
+		
+		// array methods
+
+		const_iterator begin() const;
+		const_iterator end() const;
+		iterator begin();
+		iterator end();
+		const JsonType &operator[](size_t index) const;
+		JsonType &operator[](size_t index);
+
+		// map methods
+
+		MapType::const_iterator map_begin() const;
+		MapType::const_iterator map_end() const;
+		MapType::iterator map_begin();
+		MapType::iterator map_end();
+		const JsonType &operator[](const wchar_t *key) const;
+		JsonType &operator[](const wchar_t *key);
+		bool has(std::wstring_view key) const;
+
+		// array or map methods
+
+		size_t size() const;
+		bool empty() const;
+
+	private:
+		void clear();
+
+		TypeID _type_id;
+		NumberType _number_type;
+
+		union {
+			bool b;
+			int i;
+			float f;
+			std::wstring *s;
+			ArrayType *a;
+			MapType	*o;
+		} _data;
+	};
+
+	/// \}
+
+	}
+
+}

          
A => core/core/json/json_type_helper.cpp +147 -0
@@ 0,0 1,147 @@ 
+#include "pch.h"
+
+#include <core/json/json_type_helper.h>
+#include <core/strings/utf8.h>
+#include <sstream>
+
+namespace core {
+	namespace json {
+
+bool parse(const JsonType &object, const wchar_t *key, std::wstring &result, std::string &error)
+{
+	if (!object.is_object()) {
+		error = "JSON type isn't an object";
+		return false;
+	}
+	
+	if (!object.has(key)) {
+		std::wstringstream ss;
+		ss << L"Missing " << key << L" key in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	const JsonType &value = object[key];
+	if (!value.is_string()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' must be string type in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	result = value.as_string();
+	if (result.empty()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' is empty";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	return true;
+}
+
+bool parse(const JsonType &object, const wchar_t *key, uint32_t &result, std::string &error)
+{
+	if (!object.is_object()) {
+		error = "JSON type isn't an object";
+		return false;
+	}
+	
+	if (!object.has(key)) {
+		std::wstringstream ss;
+		ss << L"Missing " << key << L" key in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	const JsonType &value = object[key];
+	if (!value.can_convert_to_uint()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' must be a positive integer type in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	result = value.as_uint();
+	return true;
+}
+
+bool parse(const JsonType &object, const wchar_t *key, int32_t &result, std::string &error)
+{
+	if (!object.is_object()) {
+		error = "JSON type isn't an object";
+		return false;
+	}
+	
+	if (!object.has(key)) {
+		std::wstringstream ss;
+		ss << L"Missing " << key << L" key in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	const JsonType &value = object[key];
+	if (!value.is_int()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' must be an integer type in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	result = value.as_int();
+	return true;
+}
+
+bool parse(const JsonType &object, const wchar_t *key, float &result, std::string &error)
+{
+	if (!object.is_object()) {
+		error = "JSON type isn't an object";
+		return false;
+	}
+	
+	if (!object.has(key)) {
+		std::wstringstream ss;
+		ss << L"Missing " << key << L" key in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	const JsonType &value = object[key];
+	if (!value.is_number()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' must be an number type in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	result = value.as_float();
+	return true;
+}
+
+bool parse(const JsonType &object, const wchar_t *key, double &result, std::string &error)
+{
+	float float_result = 0.0f;
+	bool success = parse(object, key, float_result, error);
+	if (success) {
+		result = static_cast<double>(float_result);
+	}
+	return success;
+}
+
+bool parse(const JsonType &object, const wchar_t *key, bool &result, std::string &error)
+{
+	if (!object.is_object()) {
+		error = "JSON type isn't an object";
+		return false;
+	}
+	
+	if (!object.has(key)) {
+		std::wstringstream ss;
+		ss << L"Missing " << key << L" key in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	const JsonType &value = object[key];
+	if (!value.is_bool()) {
+		std::wstringstream ss;
+		ss << L"The value of '" << key << L"' must be a boolean type in JSON object";
+		error = core::wide_to_utf8(ss.str());
+		return false;
+	}
+	result = value.as_bool();
+	return true;
+}
+
+	}
+}

          
A => core/core/json/json_type_helper.h +22 -0
@@ 0,0 1,22 @@ 
+#pragma once
+
+#include <core/json/json_type.h>
+
+namespace core {
+	namespace json {
+
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, std::wstring &result, std::string &error);
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, uint32_t &result, std::string &error);
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, int32_t &result, std::string &error);
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, float &result, std::string &error);
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, double &result, std::string &error);
+	// @return True if successful.
+	bool parse(const JsonType &object, const wchar_t *key, bool &result, std::string &error);
+
+	}
+}

          
A => core/core/json/line_counter.cpp +29 -0
@@ 0,0 1,29 @@ 
+#include "pch.h"
+
+#include <core/json/line_counter.h>
+
+namespace core {
+
+	LineCounter::LineCounter()
+		: _line(1)
+		, _column(1)
+		, _last_line(0)
+		, _last_column(0)
+	{}
+
+	void LineCounter::reset() {
+		_line = 1;
+		_column = 1;
+		_last_line = 0;
+		_last_column = 0;
+	}
+
+	void LineCounter::undo() {
+		assert(_last_line != 0); // Nothing to undo
+		_line = _last_line;
+		_column = _last_column;
+		_last_line = 0;
+		_last_column = 0;
+	}
+
+}

          
A => core/core/json/line_counter.h +62 -0
@@ 0,0 1,62 @@ 
+#pragma once
+
+namespace core {
+
+	/// \addtogroup file
+	/// \{
+
+	/// Ths class consumes utf8 characters and keeps track of the line and
+	/// column based on what is sent to it.
+	class LineCounter
+	{
+	public:
+		LineCounter();
+
+		/// Consume a character and keep track of current line and column.
+		inline void push(wchar_t c) {
+			remember_last();
+			do_push(c);
+		}
+
+		/// Unconsume the latest consumed character.
+		void undo();
+
+		/// Reset line and column to beginning again.
+		void reset();
+
+		inline uint32_t line() const {
+			return _line;
+		}
+
+		inline uint32_t column() const {
+			return _column;
+		}
+
+	private:
+		inline void do_push(wchar_t c) {
+			if (c == 13)
+				return;
+			if (c == 10) {
+				_column = 1;
+				++_line;
+				return;
+			}
+			// Advance column only if char is a single utf8 char or beginning multi utf8 char.
+			if ((c & 0x80) == 0 || (c & 0xc0) == 0xc0)
+				++_column;
+		}
+
+		inline void remember_last() {
+			_last_line = _line;
+			_last_column = _column;
+		}
+
+		uint32_t _line;
+		uint32_t _column;
+		uint32_t _last_line;
+		uint32_t _last_column;
+	};
+
+	/// \}
+
+}

          
M core/pch.h +1 -1
@@ 20,7 20,7 @@ 
 	#define LIKELY(x) __builtin_expect((x),1)
 	#define UNLIKELY(x) __builtin_expect((x),0)
 	#define forceinline inline __attribute__((always_inline))
-	#define UNREACHABLE_CODE(x)
+	#define UNREACHABLE_CODE(x) __builtin_unreachable()
 #elif defined(_MSC_VER)
 	#define LIKELY(x) x
 	#define UNLIKELY(x) x

          
M hasher/CMakeLists.txt +1 -2
@@ 20,7 20,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_F
 
 add_executable(hasher ${hasher_src})
 
-set_property(TARGET hasher PROPERTY CXX_STANDARD 17)
-set_property(TARGET hasher PROPERTY CXX_STANDARD_REQUIRED ON)
+set_property(TARGET hasher PROPERTY CXX_STANDARD 20)
 
 target_link_libraries(hasher core)

          
M jasm/CMakeLists.txt +1 -2
@@ 32,8 32,7 @@ if (${MINGW})
 	)
 endif()
 
-set_property(TARGET jasm PROPERTY CXX_STANDARD 17)
-set_property(TARGET jasm PROPERTY CXX_STANDARD_REQUIRED ON)
+set_property(TARGET jasm PROPERTY CXX_STANDARD 20)
 
 target_link_libraries(jasm core)
 

          
M jasm/assemble/assembler_impl/assembler_impl.cpp +13 -2
@@ 132,6 132,7 @@ void Assembler::fill_type_integer_operat
 	type.operators[static_cast<uint32_t>(OperatorType::BitwiseXor)] = &Assembler::operator_bitwise_xor;
 	type.operators[static_cast<uint32_t>(OperatorType::LeftShift)] = &Assembler::operator_integer_left_shift;
 	type.operators[static_cast<uint32_t>(OperatorType::RightShift)] = &Assembler::operator_integer_right_shift;
+	type.operators[static_cast<uint32_t>(OperatorType::RightArithmeticShift)] = &Assembler::operator_integer_right_arithmetic_shift;
 	type.operators[static_cast<uint32_t>(OperatorType::Equal)] = &Assembler::operator_integer_equal_to;
 	type.operators[static_cast<uint32_t>(OperatorType::NotEqual)] = &Assembler::operator_integer_not_equal_to;
 	type.operators[static_cast<uint32_t>(OperatorType::Greater)] = &Assembler::operator_integer_greater;

          
@@ 152,6 153,7 @@ void Assembler::fill_type_integer_operat
 	type.operators[static_cast<uint32_t>(OperatorType::AssignmentBitwiseXor)] = &Assembler::operator_assignment_bitwise_xor;
 	type.operators[static_cast<uint32_t>(OperatorType::AssignmentLeftShift)] = &Assembler::operator_assignment_left_shift;
 	type.operators[static_cast<uint32_t>(OperatorType::AssignmentRightShift)] = &Assembler::operator_assignment_right_shift;
+	type.operators[static_cast<uint32_t>(OperatorType::AssignmentRightArithmeticShift)] = &Assembler::operator_assignment_right_arithmetic_shift;
 	type.operators[static_cast<uint32_t>(OperatorType::PrefixIncrement)] = &Assembler::operator_unary_prefix_increment;
 	type.operators[static_cast<uint32_t>(OperatorType::PrefixDecrement)] = &Assembler::operator_unary_prefix_decrement;
 	type.operators[static_cast<uint32_t>(OperatorType::PostfixIncrement)] = &Assembler::operator_unary_postfix_increment;

          
@@ 236,6 238,7 @@ void Assembler::setup_fundamental_types(
 		type.operators[static_cast<uint32_t>(OperatorType::Less)] = &Assembler::operator_string_less;
 		type.operators[static_cast<uint32_t>(OperatorType::GreaterOrEqual)] = &Assembler::operator_string_greater_or_equal;
 		type.operators[static_cast<uint32_t>(OperatorType::LessOrEqual)] = &Assembler::operator_string_less_or_equal;
+		type.operators[static_cast<uint32_t>(OperatorType::AssignmentAdd)] = &Assembler::operator_string_assignment_add;
 		type.operators[static_cast<uint32_t>(OperatorType::ArrayAccess)] = &Assembler::operator_string_array_access;
 		type.operators[static_cast<uint32_t>(OperatorType::Period)] = &Assembler::operator_string_period;
 		type.num_properties = num_properties;

          
@@ 255,6 258,7 @@ void Assembler::setup_fundamental_types(
 		type.operators[static_cast<uint32_t>(OperatorType::Less)] = &Assembler::operator_string_less;
 		type.operators[static_cast<uint32_t>(OperatorType::GreaterOrEqual)] = &Assembler::operator_string_greater_or_equal;
 		type.operators[static_cast<uint32_t>(OperatorType::LessOrEqual)] = &Assembler::operator_string_less_or_equal;
+		type.operators[static_cast<uint32_t>(OperatorType::AssignmentAdd)] = &Assembler::operator_string_assignment_add;
 		type.operators[static_cast<uint32_t>(OperatorType::ArrayAccess)] = &Assembler::operator_string_array_access;
 		type.operators[static_cast<uint32_t>(OperatorType::Period)] = &Assembler::operator_string_period;
 		type.num_properties = num_properties;

          
@@ 606,7 610,8 @@ void Assembler::run_assembly_pass(bool g
 	_processor = _catalogue.processor(_processor_type);
 	_processor_stack.clear();
 	_processor_stack.push_back(_processor_type);
-	
+	_loop_depth_stack.clear();
+
 	_potential_warnings.clear();
 
 	// reset scopes since we may place things directly inside the global scope

          
@@ 688,9 693,15 @@ void Assembler::parse_file(bool generate
 	
 	_input_reader = TokenReader(*_input[syntax_result.token_chain_index]);
 	const SyntaxToken *t = consume_next_token();
+
+	_loop_depth_stack.push_back(0);
 	bool early_return = false;
-	t = parse_inner_scope(generate, t, early_return);
+	bool early_break = false;
+	t = parse_inner_scope(generate, t, early_return, early_break);
 	assert(!early_return); // this can't be inside a macro so this should not be possible
+	assert(!early_break); // an error should have been generated in case a break was found without a loop
+	_loop_depth_stack.pop_back();
+
 	assert(t->type == SyntaxTokenType::End);
 
 	assert(_processor_stack.size() >= processor_depth);

          
M jasm/assemble/assembler_impl/assembler_impl.h +27 -7
@@ 10,6 10,7 @@ 
 #include <core/collections/split_vector.h>
 #include <core/collections/string_hash_functor.h>
 #include <core/io/file_id.h>
+#include <core/json/json_type.h>
 #include <exceptions/assembly_exception.h>
 #include <exceptions/error_codes.h>
 #include <functional>

          
@@ 54,6 55,13 @@ constexpr uint32_t token_chain_type_buff
 constexpr uint32_t initial_section_data_size = 16384;
 constexpr uint32_t max_call_depth = 100; ///< This is used to detect never ending recursion.
 
+class RangedLoopContainer
+{
+public:
+	virtual size_t size() const = 0;
+	virtual const Value &operator[](size_t index) = 0;
+};
+
 /// @brief This monolithic class handles assembling a syntax token chain into sections of binary data.
 ///
 /// Assembling is done in several passes. Each pass tries to resolve as many constants and variables as possible.

          
@@ 455,6 463,7 @@ private:
 
 	using FloatFunction1 = double (*)(double a);
 	using FloatFunction2 = double (*)(double a, double b);
+	using StringFunction2 = std::string (*)(std::string_view a, std::string_view b);
 	using IntegerFunction2 = int32_t (*)(int32_t a, int32_t b);
 	using BooleanOperation = bool (*)(bool a, bool b);
 	using IntegerCompareOperation = bool (*)(int32_t a, int32_t b);

          
@@ 533,6 542,7 @@ private:
 	void apply_numeric_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2, FloatFunction2 float_op);
 	void apply_numeric_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2, IntegerFunction2 int_op, FloatFunction2 float_op);
 	void apply_float_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, FloatFunction2 float_op);
+	void apply_string_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, StringFunction2 string_op);
 	void apply_numeric_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, IntegerFunction2 int_op, FloatFunction2 float_op);
 	void apply_integer_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, IntegerFunction2 int_op);
 	void apply_integer_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2, IntegerFunction2 int_op);

          
@@ 571,6 581,7 @@ private:
 	void operator_string_not_equal_to(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);
 	void operator_integer_left_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);
 	void operator_integer_right_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);
+	void operator_integer_right_arithmetic_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);
 
 	void operator_integer_greater(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);
 	void operator_integer_less(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2);

          
@@ 599,6 610,7 @@ private:
 	void operator_assignment(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_integer_assignment_add(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_float_assignment_add(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
+	void operator_string_assignment_add(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_integer_assignment_subtract(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_float_assignment_subtract(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_integer_assignment_multiply(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);

          
@@ 610,6 622,7 @@ private:
 	void operator_assignment_bitwise_xor(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_assignment_left_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_assignment_right_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
+	void operator_assignment_right_arithmetic_shift(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2);
 	void operator_assignment_boolean_and(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, uint32_t next_index);
 	void operator_assignment_boolean_or(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, uint32_t next_index);
 

          
@@ 678,6 691,11 @@ private:
 	void function_print(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);
 	void function_symbol(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);
 	void function_select(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1, uint32_t arg2, uint32_t arg3);
+	void function_json_read(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);
+
+	void import_json_value(const core::json::JsonType &json_value, Value &value);
+	void import_json_array(const core::json::JsonType &array, ListDynamicObject::ListType &output);
+	void import_json_object(const core::json::JsonType &object, DictDynamicObject::DictType &output);
 
 	/// @name Print formatting
 	/// @{

          
@@ 972,21 990,21 @@ private:
 	const SyntaxToken *parse_define_data(bool generate, const SyntaxToken *t, TypeDescription *array_updated_type, const TypeDescription &type_desc, int32_t array_length);
 
 	/// Parse the loop body over a collection loop.
-	const SyntaxToken *parse_range_list_loop(bool generate, const SyntaxToken *t, const Value &loop_collection, const RangedForLoopToken &loop_token, bool &early_return);
+	const SyntaxToken *parse_range_list_loop(bool generate, const SyntaxToken *t, RangedLoopContainer &loop_container, const RangedForLoopToken &loop_token, bool &early_return);
 	const SyntaxToken *parse_range_dict_loop(bool generate, const SyntaxToken *t, const Value &loop_collection, const RangedForLoopToken &loop_token, bool &early_return);
 
-	const SyntaxToken *parse_if(bool generate, const SyntaxToken *t, bool &early_return);
+	const SyntaxToken *parse_if(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break);
 	const SyntaxToken *parse_section(bool generate, const SyntaxToken *t);
 	const SyntaxToken *parse_section_part(bool generate, const SyntaxToken *t);
-	const SyntaxToken *parse_section_map(bool generate, const SyntaxToken *t);
+	const SyntaxToken *parse_section_map(bool generate, const SyntaxToken *t, bool &early_break);
 	const SyntaxToken *parse_declare(bool generate, const SyntaxToken *t);
 	const SyntaxToken *parse_declaration(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(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break);
 	const SyntaxToken *parse_statement_after_export(bool generate, const SyntaxToken *t, const SourceLocation &location);
-	const SyntaxToken *parse_inner_scope(bool generate, const SyntaxToken *t, bool &early_return);
-	const SyntaxToken *parse_scope(bool generate, const SyntaxToken *t, bool &early_return, std::function<void ()> variable_injector = [](){});
-	const SyntaxToken *parse_namespace(bool generate, const SyntaxToken *t);
+	const SyntaxToken *parse_inner_scope(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break);
+	const SyntaxToken *parse_scope(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break, std::function<void ()> variable_injector = [](){});
+	const SyntaxToken *parse_namespace(bool generate, const SyntaxToken *t, bool &early_break);
 	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);

          
@@ 997,6 1015,7 @@ private:
 	const SyntaxToken *parse_range_for_loop(bool generate, const SyntaxToken *t, bool &early_return);
 	const SyntaxToken *parse_for_loop(bool generate, const SyntaxToken *t, bool &early_return);
 	const SyntaxToken *parse_repeat_loop(bool generate, const SyntaxToken *t, bool &early_return);
+	const SyntaxToken *parse_break(bool generate, const SyntaxToken *t, bool &early_break);
 	const SyntaxToken *parse_align(bool generate, const SyntaxToken *t);
 	const SyntaxToken *parse_using(bool generate, const SyntaxToken *t);
 	const SyntaxToken *parse_subroutine(bool generate, const SyntaxToken *t, bool export_enabled);

          
@@ 1172,6 1191,7 @@ private:
 	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<uint32_t> _loop_depth_stack; ///< A new entry, counting loop constructs, for each new file or macros definition.
 
 	std::vector<std::unique_ptr<TokenChain>> _input; ///< The syntax token stream to run assemble passes on.
 	DataReader _data_reader; ///< This handles reading binary files.

          
M jasm/assemble/assembler_impl/functions_impl.cpp +200 -19
@@ 4,11 4,13 @@ 
 #include <assemble/functions.h>
 #include <cmath>
 #include <core/environment/log.h>
+#include <core/json/json_parser.h>
 #include <core/math/sign.h>
 #include <core/strings/string_helpers.h>
 #include <core/strings/utf8.h>
 #include <iomanip>
 #include <ios>
+#include <sstream>
 
 namespace jasm {
 

          
@@ 61,6 63,7 @@ Function Assembler::function_pointer(Fun
 		{ &Assembler::function_print },
 		{ &Assembler::function_symbol },
 		{ &Assembler::function_select },
+		{ &Assembler::function_json_read },
 	};
 	static_assert(sizeof(pointers) / sizeof(pointers[0]) == static_cast<int>(FunctionType::NumTypes), "Number of functions doesn't match number of function pointers");
 	assert(type < FunctionType::NumTypes);

          
@@ 329,33 332,88 @@ void Assembler::aggregate_numeric_args(b
 {
 	Value &result = expression_values[op];
 	const Value &value1 = follow_reference_or_value(expression_values[arg1]);
-	if (!is_numeric(value1)) {
-		generate_value_type_error(generate, "numeric type", result, expression_values, components, arg1);
-		return;
-	}
+	if (is_numeric(value1)) {
+		double selected_value = dereference_float(value1);
+		const Value *selected_ptr = &value1;
+
+		// loop over the rest of the arguments and convert to floating point as necessary
+		uint32_t argument_index = components[arg1].next_sibling;
+		while (argument_index != 0) {
+			const Value &value_n = follow_reference_or_value(expression_values[argument_index]);
+			if (!is_numeric(value_n)) {
+				generate_value_type_error(generate, "numeric type", result, expression_values, components, argument_index);
+				return;
+			}
 
-	double selected_value = dereference_float(value1);
-	const Value *selected_ptr = &value1;
+			double n = dereference_float(value_n);
+			if (compare_fn(n, selected_value)) {
+				selected_value = n;
+				selected_ptr = &value_n;
+			}
+
+			argument_index = components[argument_index].next_sibling;
+		}
+
+		result.copy_value(*selected_ptr);
 
-	// loop over the rest of the arguments and convert to floating point as necessary
-	uint32_t argument_index = components[arg1].next_sibling;
-	while (argument_index != 0) {
-		const Value &value_n = follow_reference_or_value(expression_values[argument_index]);
-		if (!is_numeric(value_n)) {
-			generate_value_type_error(generate, "numeric type", result, expression_values, components, argument_index);
+	} else if (is_list(value1)) {
+
+		if (components[arg1].next_sibling != 0) {
+			if (generate) {
+				uint32_t arg2 = components[arg1].next_sibling;
+				const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg2)];
+				std::stringstream ss;
+				ss << "If first argument is a list, there can be only one argument.";
+				report_error(ec.source_location, AssemblyErrorCodes::TooManyArguments, ss.str());
+			}
+			set_unknown(result);
 			return;
 		}
 
-		double n = dereference_float(value_n);
-		if (compare_fn(n, selected_value)) {
-			selected_value = n;
-			selected_ptr = &value_n;
+		const ListDynamicObject &list_object = value1.object_reference.deref<ListDynamicObject>();
+		const std::vector<Value> &values = *list_object.value;
+
+		if (values.empty()) {
+			if (generate) {
+				const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+				std::stringstream ss;
+				ss << "List is empty.";
+				report_error(ec.source_location, AssemblyErrorCodes::ListIsEmpty, ss.str());
+			}
+			set_unknown(result);
+			return;
 		}
 
-		argument_index = components[argument_index].next_sibling;
+		double selected_value = 0;
+		const Value *selected_ptr = nullptr;
+
+		for(const Value &element_value : values) {
+			if (!is_numeric(element_value)) {
+				if (generate) {
+					const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+					std::stringstream ss;
+					ss << "List must only contain numbers but got " << to_string(element_value.type) << ".";
+					report_error(ec.source_location, AssemblyErrorCodes::UnexpectedValueType, ss.str());
+				}
+				continue;
+			}
+
+			double element_float = dereference_float(element_value);
+			if (selected_ptr == nullptr || compare_fn(element_float, selected_value)) {
+				selected_value = element_float;
+				selected_ptr = &element_value;
+			}
+		}
+
+		if (selected_ptr) {
+			result.copy_value(*selected_ptr);
+		} else {
+			set_unknown(result);
+		}
+
+	} else {
+		generate_value_type_error(generate, "numeric or list type", result, expression_values, components, arg1);
 	}
-
-	result.copy_value(*selected_ptr);
 }
 
 void Assembler::function_min(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1)

          
@@ 1452,4 1510,127 @@ void Assembler::function_select(bool gen
 	result.copy_value(expression_values[selected_arg]);
 }
 
+void Assembler::function_json_read(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1)
+{
+	Value &result = expression_values[op];
+
+	const Value &expr_value = expression_values[arg1];
+	if (!is_string(expr_value)) {
+		if (generate) {
+			const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+			std::stringstream ss;
+			ss << "Function needs string expression argument but got " << to_string(expr_value.type) << ".";
+			report_error(ec.source_location, AssemblyErrorCodes::ExpectedStringArgument, ss.str());
+		}
+		set_unknown(result);
+		return;
+	}
+
+	std::string_view filename = dereference_string(expr_value);
+	if (!_data_reader.is_known(filename)) {
+		// start reading this pass and busy wait next pass for it
+		_data_reader.queue_load(filename);
+		set_unknown(result);
+		return;
+	}
+
+	// so we did queue it earler, force load it now to get ahead
+	uint64_t handle = _data_reader.queue_load(filename);
+	const std::vector<uint8_t> *data_ptr = nullptr;
+	std::string error;
+	if (!_data_reader.data(handle, data_ptr, error))
+	{
+		const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+		report_error(ec.source_location, AssemblyErrorCodes::FailedToIncludeJson, error.c_str());
+		set_unknown(result);
+		return;
+	}
+
+	std::string_view utf8_json(reinterpret_cast<const char*>(data_ptr->data()), data_ptr->size());
+	std::wstring wide_json = core::utf8_to_wide(utf8_json);
+	std::wistringstream is(wide_json);
+	core::json::Parser parser(is);
+	core::json::JsonType object;
+	parser.parse(object);
+	if (parser.parse_error()) {
+		error = "Failed to parse json. " + parser.parse_error_string();
+		const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+		report_error(ec.source_location, AssemblyErrorCodes::JsonParseError, error.c_str());
+		set_unknown(result);
+		return;
+	}
+
+	set_dict(result);
+	result.storage_type = StorageType::Constant;
+	DictDynamicObject::DictType &contents = *result.object_reference.deref<DictDynamicObject>().value;
+
+	import_json_object(object, contents);
+}
+
+void Assembler::import_json_value(const core::json::JsonType &json_value, Value &value)
+{
+	if (json_value.is_int()) {
+		set_integer(value, json_value.as_int());
+
+	} else if (json_value.is_float()) {
+		set_float(value, json_value.as_float());
+
+	} else if (json_value.is_string()) {
+		std::string value_string = core::wide_to_utf8(json_value.as_string());
+		set_string(value, value_string);
+
+	} else if (json_value.is_array()) {
+		set_list(value);
+		ListDynamicObject::ListType &contents = *value.object_reference.deref<ListDynamicObject>().value;
+		import_json_array(json_value, contents);
+
+	} else if (json_value.is_object()) {
+		set_dict(value);
+		DictDynamicObject::DictType &contents = *value.object_reference.deref<DictDynamicObject>().value;
+		import_json_object(json_value, contents);
+
+	} else if (json_value.is_bool()) {
+		set_boolean(value, json_value.as_bool());
+
+	} else if (json_value.is_null()) {
+		set_none(value);
+
+	} else {
+		set_none(value);
+		assert(false);
+	}
+}
+
+void Assembler::import_json_array(const core::json::JsonType &array, ListDynamicObject::ListType &output)
+{
+	assert(array.is_array());
+
+	for(const core::json::JsonType &json_value : array) {
+		Value value;
+		import_json_value(json_value, value);
+		output.push_back(std::move(value));
+	}
+}
+
+void Assembler::import_json_object(const core::json::JsonType &object, DictDynamicObject::DictType &output)
+{
+	assert(object.is_object());
+
+	for(auto it = object.map_begin(); it != object.map_end(); ++it) {
+		const std::wstring &key_wstring = it->first;
+		std::string key_string = core::wide_to_utf8(key_wstring);
+		uint64_t string_hash = murmur_hash3_string_x64_64(key_string);
+		_strings.add(string_hash, key_string);
+
+		Value key;
+		Value value;
+		set_string(key, key_string);
+
+		const core::json::JsonType &json_value = it->second;
+		import_json_value(json_value, value);
+
+		output[to_dict_key(key)] = std::make_pair(key, value);
+	}
+}
+
 } // namespace jasm

          
M jasm/assemble/assembler_impl/operators_impl.cpp +85 -9
@@ 18,6 18,56 @@ const char *was_or_were(uint32_t number)
 	return "was";
 }
 
+int32_t logical_shift_left_op(int32_t a, int32_t b);
+
+int32_t logical_shift_right_op(int32_t a, int32_t b)
+{
+	if (b < 0) {
+		return logical_shift_left_op(a, -b);
+	}
+
+	if (b >= 32) {
+		return 0;
+	}
+
+	uint32_t result = static_cast<uint32_t>(a) >> core::unsign_cast(b);
+	return static_cast<int32_t>(result);
+};
+
+int32_t logical_shift_left_op(int32_t a, int32_t b)
+{
+	if (b < 0) {
+		return logical_shift_right_op(a, -b);
+	}
+
+	if (b >= 32) {
+		return 0;
+	}
+
+	uint32_t result = static_cast<uint32_t>(a) << core::unsign_cast(b);
+	return static_cast<int32_t>(result);
+};
+
+int32_t arithmetic_shift_right_op(int32_t a, int32_t b)
+{
+	if (a >= 0) {
+		return logical_shift_right_op(a, b);
+	}
+
+	if (b < 0) {
+		return logical_shift_left_op(a, -b);
+	}
+
+	if (b >= 32) {
+		return -1;
+	}
+
+	// clunky shift operation since C++ doesn't guarantee >> to be arithmetic!
+	uint32_t negative_bits = 0xffffffff << (32 - b);
+	uint32_t result = negative_bits | (static_cast<uint32_t>(a) >> core::unsign_cast(b));
+	return static_cast<int32_t>(result);
+};
+
 bool Assembler::verify_numeric_argument(bool generate, const ExpressionComponent &operator_component, const Value &right, Value &result)
 {
 	if (!is_numeric(right)) {

          
@@ 225,6 275,17 @@ void Assembler::apply_float_assignment_o
 	result = ref;
 }
 
+void Assembler::apply_string_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, StringFunction2 string_op)
+{
+	if (verify_string_argument(generate, components[operator_index], arg2, arg1)) {
+		// assign the new value
+		set_string(arg1, string_op(dereference_string(arg1), dereference_string(arg2)));
+	}
+
+	// return the reference
+	result = ref;
+}
+
 void Assembler::apply_numeric_assignment_operator(bool generate, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2, IntegerFunction2 int_op, FloatFunction2 float_op)
 {
 	if (verify_numeric_argument(generate, components[operator_index], arg2, arg1)) {

          
@@ 619,14 680,17 @@ void Assembler::operator_string_less_or_
 
 void Assembler::operator_integer_left_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2)
 {
-	auto int_op = [](int32_t a, int32_t b) { return a << b; };
-	apply_integer_operator(generate, components, operator_index, result, arg1, arg2, int_op);
+	apply_integer_operator(generate, components, operator_index, result, arg1, arg2, logical_shift_left_op);
 }
 
 void Assembler::operator_integer_right_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2)
 {
-	auto int_op = [](int32_t a, int32_t b) { return a >> b; };
-	apply_integer_operator(generate, components, operator_index, result, arg1, arg2, int_op);
+	apply_integer_operator(generate, components, operator_index, result, arg1, arg2, logical_shift_right_op);
+}
+
+void Assembler::operator_integer_right_arithmetic_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &arg1, const Value &arg2)
+{
+	apply_integer_operator(generate, components, operator_index, result, arg1, arg2, arithmetic_shift_right_op);
 }
 
 void Assembler::operator_unary_minus(bool /*generate*/, ValueVector &/*expression_values*/, const ExpressionComponent /*components*/[], uint32_t /*operator_index*/, Value &result, Value &arg1, uint32_t /*next_index*/)

          
@@ 788,6 852,12 @@ void Assembler::operator_float_assignmen
 	apply_float_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, float_op);
 }
 
+void Assembler::operator_string_assignment_add(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2)
+{
+	auto string_op = [](std::string_view a, std::string_view b) -> std::string { return std::string(a) + std::string(b); };
+	apply_string_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, string_op);
+}
+
 void Assembler::operator_integer_assignment_subtract(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2)
 {
 	auto int_op = [](int32_t a, int32_t b) { return a - b; };

          
@@ 892,14 962,17 @@ void Assembler::operator_assignment_bitw
 
 void Assembler::operator_assignment_left_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2)
 {
-	auto int_op = [](int32_t a, int32_t b) { return a << b; };
-	apply_integer_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, int_op);
+	apply_integer_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, logical_shift_left_op);
 }
 
 void Assembler::operator_assignment_right_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2)
 {
-	auto int_op = [](int32_t a, int32_t b) { return a >> b; };
-	apply_integer_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, int_op);
+	apply_integer_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, logical_shift_right_op);
+}
+
+void Assembler::operator_assignment_right_arithmetic_shift(bool generate, ValueVector &/*expression_values*/, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, const Value &arg2)
+{
+	apply_integer_assignment_operator(generate, components, operator_index, result, ref, arg1, arg2, arithmetic_shift_right_op);
 }
 
 void Assembler::operator_assignment_boolean_and(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t operator_index, Value &result, Value &ref, Value &arg1, uint32_t next_index)

          
@@ 1456,6 1529,7 @@ void Assembler::operator_call_macro(bool
 	const SyntaxToken *t = consume_next_token(); // skip macro definition
 
 	++_call_depth;
+	_loop_depth_stack.push_back(0);
 	if (_call_depth >= max_call_depth) {
 		std::stringstream ss;
 		ss << "Reached maximum call stack depth " << max_call_depth << ". Too deep call stack. Giving up assembly. There is probably a recursive macro with a broken end condition somewhere.";

          
@@ 1463,9 1537,11 @@ void Assembler::operator_call_macro(bool
 	}
 
 	bool early_return = false;
-	parse_scope(generate, t, early_return, generate_args);
+	bool early_break = false;
+	parse_scope(generate, t, early_return, early_break, generate_args);
 
 	--_call_depth;
+	_loop_depth_stack.pop_back();
 
 	if (early_return) {
 		// return the value the return statement supplied

          
M jasm/assemble/assembler_impl/syntax_impl.cpp +158 -42
@@ 14,6 14,55 @@ namespace jasm {
 
 using namespace core;
 
+class ListLoopContainer
+	: public RangedLoopContainer
+{
+public:
+	ListLoopContainer(const std::vector<Value> &list)
+		: _list(list)
+	{}
+
+	virtual size_t size() const override
+	{
+		return _list.size();
+	}
+
+	virtual const Value &operator[](size_t index) override
+	{
+		return _list[index];
+	}
+
+private:
+	const std::vector<Value> _list;
+};
+
+class StringLoopContainer
+	: public RangedLoopContainer
+{
+public:
+	StringLoopContainer(const std::wstring &string, std::function<Value(wchar_t)> &make_char_value)
+		: _string(string)
+		, _make_char_value(make_char_value)
+	{}
+
+	virtual size_t size() const override
+	{
+		return _string.size();
+	}
+
+	virtual const Value &operator[](size_t index) override
+	{
+		_temp_value = _make_char_value(static_cast<int32_t>(_string[index]));
+		return _temp_value;
+	}
+
+private:
+	std::wstring _string;
+	std::function<Value(wchar_t)> &_make_char_value;
+	Value _temp_value;
+};
+
+
 const SyntaxToken *Assembler::consume_next_scope(const SyntaxToken *t)
 {
 	uint32_t depth = 0;

          
@@ 39,13 88,16 @@ const SyntaxToken *Assembler::consume_to
 		} else if (t->type == SyntaxTokenType::ScopeEnd) {
 			if (--depth == 0)
 				break;
+		} else if (t->type == SyntaxTokenType::End) {
+			assert(depth == 1); // syntax pass should ensure we have matching curly braces
+			break;
 		}
 		t = consume_next_token();
 	}
 	return t;
 }
 
-const SyntaxToken *Assembler::parse_if(bool generate, const SyntaxToken *t, bool &early_return)
+const SyntaxToken *Assembler::parse_if(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break)
 {
 	while(true) {
 		// skip the if/elif token

          
@@ 72,7 124,7 @@ const SyntaxToken *Assembler::parse_if(b
 		bool result = dereference_boolean(value);
 		if (result) {
 			// parse the if statement
-			t = parse_scope(generate, t, early_return);
+			t = parse_scope(generate, t, early_return, early_break);
 			break;
 		}
 		t = consume_next_scope(t);

          
@@ 83,7 135,7 @@ const SyntaxToken *Assembler::parse_if(b
 		if (t->type == SyntaxTokenType::Else) {
 			// skip else token
 			t = consume_next_token();
-			t = parse_scope(generate, t, early_return);
+			t = parse_scope(generate, t, early_return, early_break);
 			break;
 		}
 

          
@@ 214,9 266,13 @@ const SyntaxToken *Assembler::parse_sect
 	}
 
 	// parse section contents
+	_loop_depth_stack.push_back(0);
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return);
+	bool early_break = false;
+	t = parse_scope(generate, t, early_return, early_break);
 	assert(!early_return); // sections are not allowed within macros so this should never happen
+	assert(!early_break); // break should have generated an error already since we pushed a new loop depth on the stack
+	_loop_depth_stack.pop_back();
 
 	// save last address to support adding section parts after this
 	_section->last_address = _program_counter.integer_value;

          
@@ 306,9 362,13 @@ const SyntaxToken *Assembler::parse_sect
 	_program_counter.integer_value = part_start_address;
 
 	// parse section contents
+	_loop_depth_stack.push_back(0);
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return);
+	bool early_break = false;
+	t = parse_scope(generate, t, early_return, early_break);
 	assert(!early_return); // section parts are not allowed within macros so this should never happen
+	assert(!early_break); // break should have generated an error already since we pushed a new loop depth on the stack
+	_loop_depth_stack.pop_back();
 
 	// add the part size to the collected size in the target section
 	int32_t part_size = _program_counter.integer_value - part_start_address;

          
@@ 321,7 381,7 @@ const SyntaxToken *Assembler::parse_sect
 }
 
 
-const SyntaxToken *Assembler::parse_section_map(bool generate, const SyntaxToken *t)
+const SyntaxToken *Assembler::parse_section_map(bool generate, const SyntaxToken *t, bool &early_break)
 {
 	const SectionMappingTokenWithMaps &maps = *static_cast<const SectionMappingTokenWithMaps *>(t);
 

          
@@ 334,7 394,7 @@ const SyntaxToken *Assembler::parse_sect
 
 	t = consume_next_token();
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return);
+	t = parse_scope(generate, t, early_return, early_break);
 	assert(!early_return); // sections are not allowed within macros so this should never happen
 
 	// restore section mappings

          
@@ 1161,10 1221,8 @@ const SyntaxToken *Assembler::skip_to_en
 	return t;
 }
 
-const SyntaxToken *Assembler::parse_range_list_loop(bool generate, const SyntaxToken *t, const Value &loop_collection, const RangedForLoopToken &loop_token, bool &early_return)
+const SyntaxToken *Assembler::parse_range_list_loop(bool generate, const SyntaxToken *t, RangedLoopContainer &loop_container, const RangedForLoopToken &loop_token, bool &early_return)
 {
-	assert(loop_collection.type == ValueType::ListReference);
-
 	// store loop position (the read position is already ahead of the for token)
 	const SyntaxToken *first_token = t;
 	TokenReadPosition loop_start = _input_reader.position_value();

          
@@ 1174,13 1232,10 @@ const SyntaxToken *Assembler::parse_rang
 	Value &loop_value = _current_pass.values.back();
 	size_t new_variable_index = _current_pass.values.size() - 1;
 
-	// make a copy of the loop vector to avoid all kinds of iteration problems since the list can be
-	// modified while iterating
-
-	std::vector<Value> collection_copy = *loop_collection.object_reference.deref<ListDynamicObject>().value;
-
 	int32_t loop_counter = 0;
-	for(const auto &value : collection_copy) {
+	for(size_t index = 0; index < loop_container.size(); ++index) {
+		const Value &value = loop_container[index];
+
 		// reset read to loop start
 		t = first_token;
 		_input_reader.set_position_value(loop_start);

          
@@ 1215,7 1270,10 @@ const SyntaxToken *Assembler::parse_rang
 			Value &iteration_value = create_unique_label(loop_symbol, global);
 			set_integer(iteration_value, loop_counter);
 		};
-		t = parse_scope(generate, t, early_return, inject_variable);
+		++_loop_depth_stack.back();
+		bool early_break = false;
+		t = parse_scope(generate, t, early_return, early_break, inject_variable);
+		--_loop_depth_stack.back();
 
 		// Remove the local variable scope for next iteration.
 		const bool uses_continue_variable = false;

          
@@ 1223,7 1281,7 @@ const SyntaxToken *Assembler::parse_rang
 
 		assert(t->type == SyntaxTokenType::RangedForLoopEnd);
 
-		if (early_return) {
+		if (early_return || early_break) {
 			break;
 		}
 

          
@@ 1292,7 1350,11 @@ const SyntaxToken *Assembler::parse_rang
 				combine_and_store_hash_name(scope_hash, symbol_hash, variable_name(loop_token.loop_value_hash, loop_token.global_value));
 			_current_pass.value_lookup[symbol_hash] = new_variable_index;
 		};
-		t = parse_scope(generate, t, early_return, inject_variable);
+
+		++_loop_depth_stack.back();
+		bool early_break = false;
+		t = parse_scope(generate, t, early_return, early_break, inject_variable);
+		--_loop_depth_stack.back();
 
 		// Remove the local variable scope for next iteration.
 		const bool uses_continue_variable = false;

          
@@ 1300,7 1362,7 @@ const SyntaxToken *Assembler::parse_rang
 
 		assert(t->type == SyntaxTokenType::RangedForLoopEnd);
 
-		if (early_return) {
+		if (early_return || early_break) {
 			break;
 		}
 

          
@@ 1331,7 1393,22 @@ const SyntaxToken *Assembler::parse_rang
 		}
 	} else {
 		if (is_list(loop_collection_value)) {
-			t = parse_range_list_loop(generate, t, loop_collection_value, loop_token, early_return);
+			std::vector<Value> &collection = *loop_collection_value.object_reference.deref<ListDynamicObject>().value;
+			// make a copy of the loop vector to avoid all kinds of iteration problems since the list can be
+			// modified while iterating
+			ListLoopContainer loop_container(collection);
+			t = parse_range_list_loop(generate, t, loop_container, loop_token, early_return);
+		} else if (is_string(loop_collection_value)) {
+			std::wstring wide_string_value = core::utf8_to_wide(dereference_string(loop_collection_value));
+			auto char_to_value = [this](wchar_t c) -> Value
+			{
+				Value v;
+				set_integer(v, static_cast<int32_t>(c));
+				return v;
+			};
+			std::function<Value(wchar_t)> func = char_to_value;
+			StringLoopContainer loop_container(wide_string_value, func);
+			t = parse_range_list_loop(generate, t, loop_container, loop_token, early_return);
 		} else {
 			if (generate) {
 				std::stringstream ss;

          
@@ 1359,8 1436,10 @@ const SyntaxToken *Assembler::parse_for_
 	// value.
 	size_t new_variable_index = _current_pass.values.size();
 	if (loop_token->has_pre_statement) {
-		t = parse_statement(generate, t, early_return);
+		bool early_break = false;
+		t = parse_statement(generate, t, early_return, early_break);
 		assert(!early_return); // return not allowed in for loop pre statement
+		assert(!early_break); // return not allowed in for loop pre statement
 	}
 
 	// store loop position (the read position is already ahead of the for token)

          
@@ 1414,7 1493,8 @@ const SyntaxToken *Assembler::parse_for_
 		}
 
 		// execute inner scope and inject the loop variable using a variable linked to the same value as the outer scope
-
+		++_loop_depth_stack.back();
+		bool early_break = false;
 		if (loop_token->has_loop_variable && loop_token->has_pre_statement) {
 			auto inject_variable = [this, loop_token, new_variable_index] {
 				uint64_t scope_hash = loop_token->global ? _symbol_environment.namespace_scope_stack.back() : _symbol_environment.local_symbol_scope_stack.back();

          
@@ 1423,16 1503,17 @@ const SyntaxToken *Assembler::parse_for_
 					combine_and_store_hash_name(scope_hash, symbol_hash, variable_name(loop_token->loop_variable_hash, loop_token->global));
 				_current_pass.value_lookup[symbol_hash] = new_variable_index;
 			};
-			t = parse_scope(generate, t, early_return, inject_variable);
+			t = parse_scope(generate, t, early_return, early_break, inject_variable);
 		} else {
-			t = parse_scope(generate, t, early_return, []{});
+			t = parse_scope(generate, t, early_return, early_break, []{});
 		}
+		--_loop_depth_stack.back();
 
 		// Remove the local variable scope for next iteration.
 		const bool uses_continue_variable = false;
 		leave_variable_scope(uses_continue_variable);
 
-		if (UNLIKELY(early_return)) {
+		if (UNLIKELY(early_return || early_break)) {
 			break;
 		}
 

          
@@ 1499,7 1580,11 @@ const SyntaxToken *Assembler::parse_repe
 				Value &new_value = create_unique_label(loop_symbol, global);
 				set_integer(new_value, loop_counter);
 			};
-			t = parse_scope(generate, t, early_return, inject_variable);
+
+			++_loop_depth_stack.back();
+			bool early_break = false;
+			t = parse_scope(generate, t, early_return, early_break, inject_variable);
+			--_loop_depth_stack.back();
 
 			assert(t->type == SyntaxTokenType::RepeatEnd);
 

          
@@ 1507,7 1592,7 @@ const SyntaxToken *Assembler::parse_repe
 			const bool uses_continue_variable = false;
 			leave_variable_scope(uses_continue_variable);
 
-			if (UNLIKELY(early_return)) {
+			if (UNLIKELY(early_return || early_break)) {
 				break;
 			}
 

          
@@ 1528,6 1613,24 @@ const SyntaxToken *Assembler::parse_repe
 }
 
 
+const SyntaxToken *Assembler::parse_break(bool generate, const SyntaxToken *t, bool &early_break)
+{
+	if (_loop_depth_stack.back() > 0) {
+		early_break = true;
+	} else {
+		if (generate) {
+			const BreakToken *expr = static_cast<const BreakToken *>(t);
+			std::stringstream ss;
+			ss << "Break outside a loop.";
+			report_error(expr->source_location, AssemblyErrorCodes::BreakWithoutLoop, ss.str());
+		}
+	}
+
+	t = consume_next_token();
+	return t;
+}
+
+
 const SyntaxToken *Assembler::parse_enum(bool generate, const SyntaxToken *t, bool export_enabled)
 {
 	const EnumDefinitionToken &enum_def = *static_cast<const EnumDefinitionToken *>(t);

          
@@ 1789,7 1892,7 @@ const SyntaxToken *Assembler::parse_usin
 }
 
 
-const SyntaxToken *Assembler::parse_namespace(bool generate, const SyntaxToken *t)
+const SyntaxToken *Assembler::parse_namespace(bool generate, const SyntaxToken *t, bool &early_break)
 {
 	const NamespaceToken &ns = *static_cast<const NamespaceToken *>(t);
 	t = consume_next_token();

          
@@ 1801,7 1904,7 @@ const SyntaxToken *Assembler::parse_name
 	};
 
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return, inject_using_namespace);
+	t = parse_scope(generate, t, early_return, early_break, inject_using_namespace);
 	assert(!early_return); // namespaces are not allowed in macros so this should not happen
 
 	leave_namespace();

          
@@ 1879,9 1982,13 @@ const SyntaxToken *Assembler::parse_modu
 		add_using_namespace(combined_hash);
 	};
 
+	_loop_depth_stack.push_back(0);
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return, inject_using_namespace);
+	bool early_break = false;
+	t = parse_scope(generate, t, early_return, early_break, inject_using_namespace);
 	assert(!early_return); // modules are not allowed within macros so this should not be possible
+	assert(!early_break); // break parsing should find errors since we create a new loop depth on the stack
+	_loop_depth_stack.pop_back();
 
 	_symbol_environment.using_scopes = saved_using_scope;
 	_symbol_environment.using_scope_size_per_scope = saved_using_scope_size_per_scope;

          
@@ 1949,9 2056,13 @@ const SyntaxToken *Assembler::parse_subr
 	// reset last generated data to detect subroutines not ending with end-of-execution instruction
 	_section->last_subroutine_generated_data = Section::Contents::Nothing;
 
+	_loop_depth_stack.push_back(0);
 	bool early_return = false;
-	t = parse_scope(generate, t, early_return);
+	bool early_break = false;
+	t = parse_scope(generate, t, early_return, early_break);
 	assert(!early_return); // subroutines are not allowed within macros so this should not happen
+	assert(!early_break); // break parsing should find errors since we create a new loop depth on the stack
+	_loop_depth_stack.pop_back();
 
 	if (generate && (_section->last_subroutine_generated_data == Section::Contents::ContinueExecutionInstruction || _section->last_subroutine_generated_data == Section::Contents::Nothing)) {
 		std::stringstream ss;

          
@@ 2157,6 2268,7 @@ const SyntaxToken *Assembler::parse_stat
 	case SyntaxTokenType::ForLoop:
 	case SyntaxTokenType::RangedForLoop:
 	case SyntaxTokenType::Repeat:
+	case SyntaxTokenType::Break:
 	case SyntaxTokenType::ScopeBegin:
 	case SyntaxTokenType::Namespace:
 	case SyntaxTokenType::Module:

          
@@ 2198,7 2310,7 @@ const SyntaxToken *Assembler::parse_stat
 }
 
 
-const SyntaxToken *Assembler::parse_statement(bool generate, const SyntaxToken *t, bool &early_return)
+const SyntaxToken *Assembler::parse_statement(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break)
 {
 	constexpr bool export_enabled = false;
 

          
@@ 2212,7 2324,7 @@ const SyntaxToken *Assembler::parse_stat
 		break;
 
 	case SyntaxTokenType::SectionMap:
-		t = parse_section_map(generate, t);
+		t = parse_section_map(generate, t, early_break);
 		break;
 
 	case SyntaxTokenType::Declare:

          
@@ 2224,11 2336,11 @@ const SyntaxToken *Assembler::parse_stat
 		break;
 
 	case SyntaxTokenType::ScopeBegin:
-		t = parse_scope(generate, t, early_return);
+		t = parse_scope(generate, t, early_return, early_break);
 		break;
 
 	case SyntaxTokenType::Namespace:
-		t = parse_namespace(generate, t);
+		t = parse_namespace(generate, t, early_break);
 		break;
 
 	case SyntaxTokenType::Module:

          
@@ 2249,7 2361,7 @@ const SyntaxToken *Assembler::parse_stat
 		break;
 
 	case SyntaxTokenType::If:
-		t = parse_if(generate, t, early_return);
+		t = parse_if(generate, t, early_return, early_break);
 		break;
 
 	case SyntaxTokenType::Expression:

          
@@ 2286,6 2398,10 @@ const SyntaxToken *Assembler::parse_stat
 		t = parse_repeat_loop(generate, t, early_return);
 		break;
 
+	case SyntaxTokenType::Break:
+		t = parse_break(generate, t, early_break);
+		break;
+
 	case SyntaxTokenType::EnumDef:
 		t = parse_enum(generate, t, export_enabled);
 		break;

          
@@ 2346,19 2462,19 @@ const SyntaxToken *Assembler::parse_stat
 }
 
 
-const SyntaxToken *Assembler::parse_inner_scope(bool generate, const SyntaxToken *t, bool &early_return)
+const SyntaxToken *Assembler::parse_inner_scope(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break)
 {
 	while (true) {
 		if (t->type == SyntaxTokenType::End || t->type == SyntaxTokenType::ScopeEnd)
 			return t;
-		t = parse_statement(generate, t, early_return);
-		if (early_return) {
+		t = parse_statement(generate, t, early_return, early_break);
+		if (early_return || early_break) {
 			return consume_to_end_of_current_scope(t);
 		}
 	}
 }
 
-const SyntaxToken *Assembler::parse_scope(bool generate, const SyntaxToken *t, bool &early_return, std::function<void ()> variable_injector)
+const SyntaxToken *Assembler::parse_scope(bool generate, const SyntaxToken *t, bool &early_return, bool &early_break, std::function<void ()> variable_injector)
 {
 	assert(t->type == SyntaxTokenType::ScopeBegin);
 	const ScopeBeginToken *begin_token = static_cast<const ScopeBeginToken *>(t);

          
@@ 2379,7 2495,7 @@ const SyntaxToken *Assembler::parse_scop
 	// allow the caller to insert initial local variables
 	variable_injector();
 
-	t = parse_inner_scope(generate, t, early_return);
+	t = parse_inner_scope(generate, t, early_return, early_break);
 
 	// check and skip the scope end token
 	assert(t->type == SyntaxTokenType::ScopeEnd);

          
M jasm/assemble/functions.cpp +2 -0
@@ 56,6 56,7 @@ const FunctionDesc &function_info(Functi
 		{not_lazy, variable_args, 1}, // print
 		{not_lazy, fixed_args, 1}, // symbol
 		{is_lazy, fixed_args, 3}, // select
+		{not_lazy, fixed_args, 1}, // json_read
 	};
 	static_assert(sizeof(desc) / sizeof(desc[0]) == static_cast<int>(FunctionType::NumTypes), "Number of functions doesn't match number of decl structs");
 

          
@@ 110,6 111,7 @@ std::string_view to_string(FunctionType 
 		std::string_view("print"),
 		std::string_view("symbol"),
 		std::string_view("select"),
+		std::string_view("json_read"),
 	};
 	static_assert(sizeof(names) / sizeof(names[0]) == static_cast<size_t>(FunctionType::NumTypes), "Number of functions doesn't match number of strings");
 

          
M jasm/assemble/functions.h +1 -0
@@ 51,6 51,7 @@ enum class FunctionType : uint8_t
 	Print,
 	Symbol,
 	Select,
+	JsonRead,
 	NumTypes,
 };
 

          
M jasm/assemble/value.h +2 -1
@@ 452,7 452,8 @@ public:
 
 	/// This has to be a pointer to allow list element references to redirect a vector to a new one.
 	/// In essence, the list has to be a pointer type object itself.
-	std::shared_ptr<std::vector<Value>> value;
+	using ListType = std::vector<Value>;
+	std::shared_ptr<ListType> value;
 };
 
 /*

          
M jasm/docs/jasm.md +99 -14
@@ 107,6 107,7 @@ This documentation covers the language a
    * [Conditional Assembly](#conditional-assembly)
    * [Include Source](#include-source)
    * [Include Data](#include-data)
+   * [JSON](#json-data)
    * [Defining Data](#defining-data)
    * [Reserving Space](#reserving-space)
    * [Subroutines](#subroutines)

          
@@ 115,6 116,7 @@ This documentation covers the language a
       * [For Loop](#for-loops)
       * [Range-based For Loop](#range-based-for-loops)
       * [Repeat Loop](#repeat-loops)
+      * [Break Loops](#break-loops)
    * [Macros](#macros)
    * [Alignment](#alignment)
 

          
@@ 130,6 132,12 @@ jAsm supports all regular instructions o
 	lda #0
 	sta $d020
 
+The `[6502]|brk` instruction takes an optional immediate argument since `[6502]|rti` actually will return to the instruction after that argument (this goes for 65C02, 65CE02 and 45GS02 as well).
+
+	[6502]
+	brk // valid but rti won't return directly after this instruction
+	brk #0 // optional argument makes rti return to next instruction
+
 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]

          
@@ 1852,8 1860,9 @@ jAsm supports a number of operators, sim
 	<tr><td><code>[6502]|/</code></td><td>division</td><td>integer, float</td><td><code>[6502]|aa / bb</code></td></tr>
 	<tr><td><code>[6502]|+</code></td><td>addition</td><td>integer, float, string, list</td><td><code>[6502]|aa + bb</code></td></tr>
 	<tr><td><code>[6502]|-</code></td><td>subtraction</td><td>integer, float</td><td><code>[6502]|aa - bb</code></td></tr>
-	<tr><td><code>[6502]|&lt;&lt;</code></td><td>left shift</td><td>integer</td><td><code>[6502]|aa &lt;&lt; 1</code></td></tr>
-	<tr><td><code>[6502]|&gt;&gt;</code></td><td>right shift</td><td>integer</td><td><code>[6502]|aa &gt;&gt; 1</code></td></tr>
+	<tr><td><code>[6502]|&lt;&lt;</code></td><td>logical left shift</td><td>integer</td><td><code>[6502]|aa &lt;&lt; 1</code></td></tr>
+	<tr><td><code>[6502]|&gt;&gt;</code></td><td>logical right shift</td><td>integer</td><td><code>[6502]|aa &gt;&gt; 1</code></td></tr>
+	<tr><td><code>[6502]|&gt;&gt;&gt;</code></td><td>arithmetic right shift</td><td>integer</td><td><code>[6502]|aa &gt;&gt;&gt; 1</code></td></tr>
 	<tr><td><code>[6502]|&amp;</code></td><td>bitwise and</td><td>integer</td><td><code>[6502]|aa &amp; bb</code></td></tr>
 	<tr><td><code>[6502]|^</code></td><td>bitwise exclusive or</td><td>integer</td><td><code>[6502]|ab ^ bb</code></td></tr>
 	<tr><td><code>[6502]||</code></td><td>bitwise or</td><td>integer</td><td><code>[6502]|aa | bb</code></td></tr>

          
@@ 1866,7 1875,7 @@ jAsm supports a number of operators, sim
 	<tr><td><code>[6502]|&amp;&amp;</code></td><td>boolean and</td><td>boolean</td><td><code>[6502]|aa &amp;&amp; bb</code></td></tr>
 	<tr><td><code>[6502]|||</code></td><td>boolean or</td><td>boolean</td><td><code>[6502]|aa || bb</code></td></tr>
 	<tr><td><code>[6502]|=</code></td><td>assignment</td><td>all</td><td><code>[6502]|aa = 1</code></td></tr>
-	<tr><td><code>[6502]|+=</code></td><td>add and assign</td><td>integer, float, list</td><td><code>[6502]|aa += 1</code></td></tr>
+	<tr><td><code>[6502]|+=</code></td><td>add and assign</td><td>integer, float, string, list</td><td><code>[6502]|aa += 1</code></td></tr>
 	<tr><td><code>[6502]|-=</code></td><td>subtract and assign</td><td>integer, float</td><td><code>[6502]|aa -= 1</code></td></tr>
 	<tr><td><code>[6502]|*=</code></td><td>multiply and assign</td><td>integer, float</td><td><code>[6502]|aa *= 2</code></td></tr>
 	<tr><td><code>[6502]|/=</code></td><td>divide and assign</td><td>integer, float</td><td><code>[6502]|aa /= 2</code></td></tr>

          
@@ 1875,8 1884,9 @@ jAsm supports a number of operators, sim
 	<tr><td><code>[6502]|&amp;=</code></td><td>bitwise and, and assign</td><td>integer</td><td><code>[6502]|aa &amp;= bb</code></td></tr>
 	<tr><td><code>[6502]||=</code></td><td>bitwise or, and assign</td><td>integer</td><td><code>[6502]|aa |= bb</code></td></tr>
 	<tr><td><code>[6502]|^=</code></td><td>bitwise exclusive or, and assign</td><td>integer</td><td><code>[6502]|aa ^= bb</code></td></tr>
-	<tr><td><code>[6502]|&lt;&lt;=</code></td><td>left shift, and assign</td><td>integer</td><td><code>[6502]|aa &lt;&lt;= 1</code></td></tr>
-	<tr><td><code>[6502]|&gt;&gt;=</code></td><td>right shift, and assign</td><td>integer</td><td><code>[6502]|aa &gt;&gt;= 1</code></td></tr>
+	<tr><td><code>[6502]|&lt;&lt;=</code></td><td>logical left shift, and assign</td><td>integer</td><td><code>[6502]|aa &lt;&lt;= 1</code></td></tr>
+	<tr><td><code>[6502]|&gt;&gt;=</code></td><td>logical right shift, and assign</td><td>integer</td><td><code>[6502]|aa &gt;&gt;= 1</code></td></tr>
+	<tr><td><code>[6502]|&gt;&gt;&gt;=</code></td><td>arithmetic right shift, and assign</td><td>integer</td><td><code>[6502]|aa &gt;&gt;&gt;= 1</code></td></tr>
 </table>
 
 <div id="statements"></div>

          
@@ 2043,7 2053,7 @@ The following operators and methods are 
 To support different platform's character sets there is a `[6502]|string()` function that is used to convert unicode strings, which is the default string type, to other character sets.
 
 	[6502]
-	const PET_HELLO = string("Hello", "petscii", "lowercase")
+	const PET_HELLO = string("Hello", "pet", "lowercase")
 
 The function takes a number of arguments. First the string to convert, then a number of conversion properties which specifies the format, subformat, locale and flags for the conversion, in any order.
 

          
@@ 2059,8 2069,32 @@ The following format properties are supp
 		<td>7 bit ascii format.</td>
 	</tr>
 	<tr>
-		<td>petscii</td>
-		<td>The character set used in Commodore 8 bit computers.</td>
+		<td>pet</td>
+		<td>The character set used in Commodore PET models after PET 2001.</td>
+	</tr>
+	<tr>
+		<td>pet2001</td>
+		<td>The character set used in the Commodore PET 2001 model.</td>
+	</tr>
+	<tr>
+		<td>vic20</td>
+		<td>The character set used in the Commodore VIC 20.</td>
+	</tr>
+	<tr>
+		<td>c16</td>
+		<td>The character set used in the Commodore 16.</td>
+	</tr>
+	<tr>
+		<td>plus4</td>
+		<td>The character set used in the Commodore Plus/4.</td>
+	</tr>
+	<tr>
+		<td>c64</td>
+		<td>The character set used in the Commodore 64.</td>
+	</tr>
+	<tr>
+		<td>c128</td>
+		<td>The character set used in the Commodore 128.</td>
 	</tr>
 	<tr>
 		<td>zx80</td>

          
@@ 2087,18 2121,18 @@ The following optional subformats are su
 	</tr>
 	<tr>
 		<td>lowercase</td>
-		<td>petscii</td>
-		<td>The petscii character set with both lower and uppercase characters.</td>
+		<td>pet2001, pet, vic20, c16, plus4, c64, c128</td>
+		<td>The character set with both lower and uppercase characters.</td>
 	</tr>
 	<tr>
 		<td>uppercase_screen</td>
-		<td>petscii</td>
-		<td>The petscii character set as screen codes.</td>
+		<td>pet2001, pet, vic20, c16, plus4, c64, c128</td>
+		<td>The character set as screen codes.</td>
 	</tr>
 	<tr>
 		<td>lowercase_screen</td>
-		<td>petscii</td>
-		<td>The petscii character set with both lower and uppercase characters as screen codes.</td>
+		<td>pet2001, pet, vic20, c16, plus4, c64, c128</td>
+		<td>The character set with both lower and uppercase characters as screen codes.</td>
 	</tr>
 </table>
 

          
@@ 2549,12 2583,24 @@ jAsm provides a number of mathematical f
 		<td><code>[6502]|max(2, 4) // 4</code><br/><code>[6502]|max(2, 4.0) // 4.0</code></td>
 	</tr>
 	<tr>
+		<td><code>[6502]|max(a)</code></td>
+		<td>list</td>
+		<td>Returns the largest of the numeric elements in the list.</td>
+		<td><code>[6502]|max(list(2, 4)) // 4</code><br/><code>[6502]|max(list(2, 4.0)) // 4.0</code></td>
+	</tr>
+	<tr>
 		<td><code>[6502]|min(a, ...)</code></td>
 		<td>numeric</td>
 		<td>Returns the smallest of the arguments.</td>
 		<td><code>[6502]|min(2, 4) // 2</code><br/><code>[6502]|min(2, 4.0) // 2</code></td>
 	</tr>
 	<tr>
+		<td><code>[6502]|min(a)</code></td>
+		<td>list</td>
+		<td>Returns the smallest of the numeric elements in the list.</td>
+		<td><code>[6502]|min(list(2, 4)) // 2</code><br/><code>[6502]|min(list(2, 4.0)) // 2</code></td>
+	</tr>
+	<tr>
 		<td><code>[6502]|modulo(a, b)</code></td>
 		<td>integer, integer</td>
 		<td>Returns the remainder from the Euclidean division a/b.</td>

          
@@ 3105,6 3151,16 @@ This will skip the first two bytes of th
 
 This will read at most 4 bytes from offset 2 in the specified file.
 
+<div id="json-data"></div>
+## JSON
+
+Data in JSON format can also be imported into a variable using the `[6502]|json_read` function. The entire file will be imported as dictionary data.
+
+	[6502]
+	const imported_data = json_read("some_dir/some_file.json")
+
+This can be used to get compile settings into the build or data from a previous compile into the next.
+
 <div id="defining-data"></div>
 ## Defining Data
 

          
@@ 3332,6 3388,18 @@ This will generate data for filenames wh
 
 *When the loop starts, a copy of the list will be made. The iteration is done over the copy to avoid problems where the list is accidently modified inside the loop.*
 
+Strings can also be iterated over with this form of loop.
+
+	[6502]
+	const .message = "SECRET"
+
+	for(var .char in .message)
+	{
+		define byte[] = { .char ^ $ff }
+	}
+
+This will store an encoded string where each character has all its bits flipped.
+
 This type of loop can be used on dicts as well like this.
 
 	[6502]

          
@@ 3358,6 3426,23 @@ The `[6502]|repeat` loop is a simplified
 
 This defines numbers 0, 1, 2, 3 and 4 in memory.
 
+<div id="break-loops"></div>
+### Break Loops
+
+The `[6502]|break` statement can be used in any form of loop to exit it prematurely.
+
+	[6502]
+	repeat 5
+	{
+		if (@i == 3)
+		{
+			break
+		}
+		define byte = @i
+	}
+
+This defines numbers 0, 1 and 2 in memory since the loop is exited before the end condition is reached.
+
 <div id="macros"></div>
 ## Macros
 

          
M jasm/exceptions/error_codes.h +6 -2
@@ 23,10 23,10 @@ enum class AssemblyErrorCodes
 	TooLongCharacterConstant,
 	MultilineCommentWasNotTerminated,
 	MissingClosingStringQuote,
-	Unused1, // not used!
+	UnexpectedEndOfStringOrLiteral,
 	IllegalCharacterInBinaryConstant,
 	IllegalCharacterInHexConstant,
-	Unused2, // not used!
+	UnexpectedEndOfNumeric,
 	UnmatchedProcessorPop,
 	InvalidProcessorName,
 	ExpectedProcessorNameOrPop,

          
@@ 208,6 208,10 @@ enum class AssemblyErrorCodes
 	MacroValueExpected,
 	ExpectedBooleanResult,
 	SymbolRecursion,
+	ListIsEmpty,
+	BreakWithoutLoop,
+	FailedToIncludeJson,
+	JsonParseError,
 
 	// assembler warnings
 	SubroutineFallthrough = 3500,

          
M jasm/io/data_reader.cpp +5 -0
@@ 61,6 61,11 @@ uint64_t DataReader::queue_load(const st
 	return handle;
 }
 
+bool DataReader::is_known(const std::string_view &filename)
+{
+	uint64_t handle = murmur_hash3_string_x64_64(filename);
+	return _handle_map.find(handle) != _handle_map.end();
+}
 
 bool DataReader::size(uint64_t handle, size_t &size, std::string &error)
 {

          
M jasm/io/data_reader.h +3 -0
@@ 28,6 28,9 @@ public:
 	/// @return Handle to the load request.
 	uint64_t queue_load(const std::string_view &filename);
 
+	/// @return True if the file has been queued for loading.
+	bool is_known(const std::string_view &filename);
+
 	/// This will block until the size has been determined and then return it or a fail state.
 	/// @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.

          
M jasm/pch.h +1 -1
@@ 20,7 20,7 @@ 
 	#define LIKELY(x) __builtin_expect((x),1)
 	#define UNLIKELY(x) __builtin_expect((x),0)
 	#define forceinline inline __attribute__((always_inline))
-	#define UNREACHABLE_CODE(x)
+	#define UNREACHABLE_CODE(x) __builtin_unreachable()
 	#define SINGLE_INHERITANCE
 #elif defined(_MSC_VER)
 	#define LIKELY(x) x

          
M jasm/processor/45gs02/instructions_45gs02.cpp +2 -2
@@ 81,7 81,7 @@ uint32_t __addressing_modes_mask[static_
 	/* BMI */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
 	/* BNE */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
 	/* BPL */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
-	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | Imp | ___ | ____ | __ | ___ ,
+	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | Imp | Imm | ____ | __ | ___ ,
 	/*        Bp   Abs   Bpx   AbsX   Bpy   AbsY   BpIX   IndX   Rel   RelW   BInd   Ind   BpIY   BpIZ | BIQ | BIQZ   SpIY   Imp   Imm   ImmW   Bb   Bbr */
 	/* BRU */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
 	/* BSR */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | RelW | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,

          
@@ 257,7 257,7 @@ OpCodes __opcodes[static_cast<int>(Instr
 	/* BPL */ {{op(), op(), op(), op(), op(), op(), op(), op(), op(0x10), op(0x13), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op()}},
 
 	/*          BP,   ABSO, BPX,  ABSX, BPY,  ABSY, BPIX, INDX, RELA, RELW, BIND, INDI, BPIY, BPIZ, BIQ,  BIQZ, SPIY, IMPL,     IMME, IMMW, BB,   BBR,*/
-	/* BRK */ {{op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(0x00), op(), op(), op(), op()}},
+	/* BRK */ {{op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(0x00), op(0x00), op(), op(), op()}},
 
 	/*          BP,   ABSO, BPX,  ABSX, BPY,  ABSY, BPIX, INDX, RELA,     RELW,     BIND, INDI, BPIY, BPIZ, BIQ,  BIQZ, SPIY, IMPL, IMME, IMMW, BB,   BBR,*/
 	/* BRU */ {{op(), op(), op(), op(), op(), op(), op(), op(), op(0x80), op(0x83), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op()}},

          
M jasm/processor/6502/instructions_6502.cpp +1 -1
@@ 26,7 26,7 @@ uint16_t __addressing_modes_mask[static_
 	/* BMI */ ___ | ___ | __ | ___ | ___ | ___ | ____ | ____ | Rel | ___ | ____ | ____ ,
 	/* BNE */ ___ | ___ | __ | ___ | ___ | ___ | ____ | ____ | Rel | ___ | ____ | ____ ,
 	/* BPL */ ___ | ___ | __ | ___ | ___ | ___ | ____ | ____ | Rel | ___ | ____ | ____ ,
-	/* BRK */ Imp | ___ | __ | ___ | ___ | ___ | ____ | ____ | ___ | ___ | ____ | ____ ,
+	/* BRK */ Imp | Imm | __ | ___ | ___ | ___ | ____ | ____ | ___ | ___ | ____ | ____ ,
 	/* BVC */ ___ | ___ | __ | ___ | ___ | ___ | ____ | ____ | Rel | ___ | ____ | ____ ,
 	/* BVS */ ___ | ___ | __ | ___ | ___ | ___ | ____ | ____ | Rel | ___ | ____ | ____ ,
 	/* CLC */ Imp | ___ | __ | ___ | ___ | ___ | ____ | ____ | ___ | ___ | ____ | ____ ,

          
M jasm/processor/65c02/instructions_65c02.cpp +1 -1
@@ 29,7 29,7 @@ uint16_t __addressing_modes_mask[static_
 	/* BNE */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | ___ | ___ | __ | ___ | Rel ,
 	/* BPL */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | ___ | ___ | __ | ___ | Rel ,
 	/* BRA */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | ___ | ___ | __ | ___ | Rel ,
-	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | Imp | ___ | __ | ___ | ___ ,
+	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | Imp | Imm | __ | ___ | ___ ,
 	/* BVC */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | ___ | ___ | __ | ___ | Rel ,
 	/* BVS */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | ___ | ___ | __ | ___ | Rel ,
 	/* CLC */ __ | ___ | ___ | ____ | ___ | ____ | ___ | ___ | ____ | ____ | ____ | Imp | ___ | __ | ___ | ___ ,

          
M jasm/processor/65ce02/instructions_65ce02.cpp +1 -1
@@ 31,7 31,7 @@ uint32_t __addressing_modes_mask[static_
 	/* BMI */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,
 	/* BNE */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,
 	/* BPL */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,
-	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ____ | ____ | ___ | Imp | ___ | ____ | __ | ___ ,
+	/* BRK */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ____ | ____ | ___ | Imp | Imm | ____ | __ | ___ ,
 	/* BRU */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,
 	/* BSR */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,
 	/* BVC */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | Rel | RelW | ____ | ____ | ____ | ___ | ___ | ___ | ____ | __ | ___ ,

          
M jasm/processor/processor.cpp +3 -0
@@ 87,6 87,7 @@ void Processor::init(bool pseudo_instruc
 		OperatorType::Minus,
 		OperatorType::LeftShift,
 		OperatorType::RightShift,
+		OperatorType::RightArithmeticShift,
 		OperatorType::Less,
 		OperatorType::Greater,
 		OperatorType::LessOrEqual,

          
@@ 110,6 111,7 @@ void Processor::init(bool pseudo_instruc
 		OperatorType::AssignmentBitwiseXor,
 		OperatorType::AssignmentLeftShift,
 		OperatorType::AssignmentRightShift,
+		OperatorType::AssignmentRightArithmeticShift,
 		OperatorType::Colon,
 		OperatorType::Namespace,
 		OperatorType::Semicolon,

          
@@ 200,6 202,7 @@ const Processor *ProcessorCatalogue::pro
 		case ProcessorType::Zilog80:
 			return _z80.get();
 	}
+	UNREACHABLE_CODE(throw Exception("internal error"));
 }
 
 }

          
M jasm/strings/string_conversions.cpp +407 -20
@@ 26,7 26,13 @@ namespace {
 }
 
 const std::string_view StringConversions::_formats[static_cast<size_t>(StringConversions::Format::NumFormats)] = {
-	std::string_view("petscii"),
+	std::string_view("pet2001"),
+	std::string_view("pet"),
+	std::string_view("vic20"),
+	std::string_view("c16"),
+	std::string_view("plus4"),
+	std::string_view("c64"),
+	std::string_view("c128"),
 	std::string_view("zx80"),
 	std::string_view("zx81"),
 	std::string_view("ascii7"),

          
@@ 54,17 60,367 @@ StringConversions::StringConversions()
 	static_assert(core::array_num_elements(StringConversions::_locales) == static_cast<size_t>(StringConversions::Locale::NumLocales), "Formats mismatch");
 	static_assert(core::array_num_elements(StringConversions::_flags) == static_cast<size_t>(StringConversions::NumFlags), "Formats mismatch");
 
-	_conversions.insert(format_hash(Format::Petscii, SubFormat::Lowercase, Locale::English));
-	_conversions.insert(format_hash(Format::Petscii, SubFormat::LowercaseScreen, Locale::English));
-	_conversions.insert(format_hash(Format::Petscii, SubFormat::Uppercase, Locale::English));
-	_conversions.insert(format_hash(Format::Petscii, SubFormat::UppercaseScreen, Locale::English));
-	_conversions.insert(format_hash(Format::ZX80, SubFormat::Default, Locale::English));
-	_conversions.insert(format_hash(Format::ZX81, SubFormat::Default, Locale::English));
-	_conversions.insert(format_hash(Format::Ascii7, SubFormat::Default, Locale::English));
+	// -------
+	// PET2001
+	// -------
+
+	{
+		// fill pet2001 lowercase
+		ConversionMap map;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = c + 32;
+		map[L'@'] = 64;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'A'] = c + 65;
+		map[L'['] = 0x5b;
+		map[L'\\'] = 0x5c;
+		map[L']'] = 0x5d;
+		map[L'\u2191'] = 0x5e; // arrow up
+		map[L'\u2190'] = 0x5f; // arrow left
+		map[L'\u2501'] = 0xc0; // horizontal line
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + 'a'] = c + 0xc1;
+		map[L'\u254b'] = 0xdb; // junction
+		map[L'\u2503'] = 0xdd; // vertical line
+
+		map[L'\u2592'] = 0xa6; // medium checker
+
+		map[L'\u2523'] = 0xab; // vertical and right crossing
+		map[L'\u2597'] = 0xac; // quadrant lower right
+		map[L'\u2517'] = 0xad; // up and right bend
+		map[L'\u2513'] = 0xae; // down and left bend
+		map[L'\u250f'] = 0xb0; // down and right bend
+		map[L'\u253b'] = 0xb1; // up and horizontal crossing
+		map[L'\u2533'] = 0xb2; // down and horizontal crossing
+		map[L'\u252b'] = 0xb3; // vertical and left crossing
+		map[L'\u2596'] = 0xbb; // quadrant lower left
+		map[L'\u259d'] = 0xbc; // quadrant upper right
+		map[L'\u251b'] = 0xbd; // up and left bend
+		map[L'\u2598'] = 0xbe; // quadrant upper left
+		map[L'\u259a'] = 0xbf; // quadrant upper left lower right
+
+		 _conversions[format_hash(Format::Pet2001, SubFormat::Lowercase, Locale::English)] = map;
+	}
+
+	{
+		// fill pet2001 uppercase
+		ConversionMap map;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = c + 32;
+		map[L'@'] = 64;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'A'] = c + 65;
+		map[L'['] = 0x5b;
+		map[L'\\'] = 0x5c;
+		map[L']'] = 0x5d;
+		map[L'\u2191'] = 0x5e; // arrow up
+		map[L'\u2190'] = 0x5f; // arrow left
+		map[L'\u2501'] = 0xc0; // horizontal line
+		map[L'\u2660'] = 0xc1; // spades
+		map[L'\u2572'] = 0xcd; // nw diagonal line
+		map[L'\u2571'] = 0xce; // ne diagonal line
+		map[L'\u25cf'] = 0xd1; // filled circle
+		map[L'\u2665'] = 0xd3; // heart
+		map[L'\u2573'] = 0xd6; // cross
+		map[L'\u25cb'] = 0xd7; // circle
+		map[L'\u2663'] = 0xd8; // club
+		map[L'\u2666'] = 0xda; // diamond
+		map[L'\u254b'] = 0xdb; // junction
+		map[L'\u2503'] = 0xdd; // vertical line
+		map[L'\u23c0'] = 0xde; // pi
+		map[L'\u25e5'] = 0xdf; // upper right triangle
+		map[L'\u2592'] = 0xa6; // medium checker
+		map[L'\u25e4'] = 0xe9; // upper left triangle
+
+		map[L'\u2523'] = 0xab; // vertical and right crossing
+		map[L'\u2597'] = 0xac; // quadrant lower right
+		map[L'\u2517'] = 0xad; // up and right bend
+		map[L'\u2513'] = 0xae; // down and left bend
+		map[L'\u250f'] = 0xb0; // down and right bend
+		map[L'\u253b'] = 0xb1; // up and horizontal crossing
+		map[L'\u2533'] = 0xb2; // down and horizontal crossing
+		map[L'\u252b'] = 0xb3; // vertical and left crossing
+		map[L'\u2596'] = 0xbb; // quadrant lower left
+		map[L'\u259d'] = 0xbc; // quadrant upper right
+		map[L'\u251b'] = 0xbd; // up and left bend
+		map[L'\u2598'] = 0xbe; // quadrant upper left
+		map[L'\u259a'] = 0xbf; // quadrant upper left lower right
+
+		_conversions[format_hash(Format::Pet2001, SubFormat::Uppercase, Locale::English)] = map;
+	}
+
+	{
+		// fill pet2001 screencode uppercase
+		ConversionMap map;
+		map[L'@'] = 0;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'A'] = c + 1;
+		map[L'['] = 27;
+		map[L'\\'] = 28;
+		map[L']'] = 29;
+		map[L'\u2191'] = 30;
+		map[L'\u2190'] = 31;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = c + 32;
+
+		map[L'\u2501'] = 0x40; // horizontal line
+		map[L'\u2660'] = 0x41; // spades
+		map[L'\u2572'] = 0x4d; // nw diagonal line
+		map[L'\u2571'] = 0x4e; // ne diagonal line
+		map[L'\u25cf'] = 0x51; // filled circle
+		map[L'\u2665'] = 0x53; // heart
+		map[L'\u2573'] = 0x56; // cross
+		map[L'\u25cb'] = 0x57; // circle
+		map[L'\u2663'] = 0x58; // club
+		map[L'\u2666'] = 0x5a; // diamond
+		map[L'\u254b'] = 0x5b; // junction
+		map[L'\u2503'] = 0x5d; // vertical line
+
+		map[L'\u23c0'] = 0x5e; // pi
+		map[L'\u25e5'] = 0x5f; // upper right triangle
+		map[L'\u2592'] = 0x66; // medium checker
+		map[L'\u25e4'] = 0x69; // upper left triangle
+
+		map[L'\u2523'] = 0x6b; // vertical and right crossing
+		map[L'\u2597'] = 0x6e; // quadrant lower right
+		map[L'\u2517'] = 0x6d; // up and right bend
+		map[L'\u2513'] = 0x6e; // down and left bend
+		map[L'\u250f'] = 0x70; // down and right bend
+		map[L'\u253b'] = 0x71; // up and horizontal crossing
+		map[L'\u2533'] = 0x72; // down and horizontal crossing
+		map[L'\u252b'] = 0x73; // vertical and left crossing
+		map[L'\u2596'] = 0x7b; // quadrant lower left
+		map[L'\u259d'] = 0x7c; // quadrant upper right
+		map[L'\u251b'] = 0x7d; // up and left bend
+		map[L'\u2598'] = 0x7e; // quadrant upper right
+		map[L'\u259a'] = 0x7f; // quadrant upper left lower right
+
+		_conversions[format_hash(Format::Pet2001, SubFormat::UppercaseScreen, Locale::English)] = map;
+	}
+
+	{
+		// fill pet2001 screencode lowercase
+		ConversionMap map;
+		map[L'@'] = 0;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'A'] = c + 1;
+		map[L'['] = 27;
+		map[L'\\'] = 28;
+		map[L']'] = 29;
+		map[L'\u2191'] = 30;
+		map[L'\u2190'] = 31;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = c + 32;
+
+		map[L'\u2501'] = 0x40; // horizontal line
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'a'] = 0x41 + c;
+		map[L'\u254b'] = 0x5b; // junction
+		map[L'\u2503'] = 0x5d; // vertical line
+
+		map[L'\u2592'] = 0x66; // medium checker
+
+		map[L'\u2523'] = 0x6b; // vertical and right crossing
+		map[L'\u2597'] = 0x6e; // quadrant lower right
+		map[L'\u2517'] = 0x6d; // up and right bend
+		map[L'\u2513'] = 0x6e; // down and left bend
+		map[L'\u250f'] = 0x70; // down and right bend
+		map[L'\u253b'] = 0x71; // up and horizontal crossing
+		map[L'\u2533'] = 0x72; // down and horizontal crossing
+		map[L'\u252b'] = 0x73; // vertical and left crossing
+		map[L'\u2596'] = 0x7b; // quadrant lower left
+		map[L'\u259d'] = 0x7c; // quadrant upper right
+		map[L'\u251b'] = 0x7d; // up and left bend
+		map[L'\u2598'] = 0x7e; // quadrant upper right
+		map[L'\u259a'] = 0x7f; // quadrant upper left lower right
+
+		_conversions[format_hash(Format::Pet2001, SubFormat::UppercaseScreen, Locale::English)] = map;
+	}
+
+	// ---------------------
+	// PET (other than 2001)
+	// ---------------------
+
+	{
+		// fill pet lowercase
+		ConversionMap map;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = 32 + c;
+		map[L'@'] = 64;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'a'] = c + 0x41;
+		map[L'['] = 0x5b;
+		map[L'\\'] = 0x5c;
+		map[L']'] = 0x5d;
+		map[L'\u2191'] = 0x5e; // arrow up
+		map[L'\u2190'] = 0x5f; // arrow left
+		map[L'\u2501'] = 0xc0; // horizontal line
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + 'A'] = c + 0xc1;
+		map[L'\u254b'] = 0xdb; // junction
+		map[L'\u2503'] = 0xdd; // vertical line
+
+		map[L'\u2592'] = 0xa6; // medium checker
+
+		map[L'\u2523'] = 0xab; // vertical and right crossing
+		map[L'\u2597'] = 0xac; // quadrant lower right
+		map[L'\u2517'] = 0xad; // up and right bend
+		map[L'\u2513'] = 0xae; // down and left bend
+		map[L'\u250f'] = 0xb0; // down and right bend
+		map[L'\u253b'] = 0xb1; // up and horizontal crossing
+		map[L'\u2533'] = 0xb2; // down and horizontal crossing
+		map[L'\u252b'] = 0xb3; // vertical and left crossing
+		map[L'\u2596'] = 0xbb; // quadrant lower left
+		map[L'\u259d'] = 0xbc; // quadrant upper right
+		map[L'\u251b'] = 0xbd; // up and left bend
+		map[L'\u2598'] = 0xbe; // quadrant upper left
+		map[L'\u259a'] = 0xbf; // quadrant upper left lower right
+
+		 _conversions[format_hash(Format::Pet, SubFormat::Lowercase, Locale::English)] = map;
+	}
+
+	{
+		// fill pet uppercase
+		ConversionMap map;
+		for (wchar_t c = 0; c < 32; ++c)
+			map[c + 32] = c + 32;
+		map[L'@'] = 64;
+		for (wchar_t c = 0; c < 26; ++c)
+			map[c + L'A'] = 0x41 + c;
+		map[L'['] = 0x5b;
+		map[L'\\'] = 0x5c;
+		map[L']'] = 0x5d;
+		map[L'\u2191'] = 0x5e; // arrow up
+		map[L'\u2190'] = 0x5f; // arrow left
+		map[L'\u2501'] = 0xc0; // horizontal line
+		map[L'\u2660'] = 0xc1; // spades
+		map[L'\u2572'] = 0xcd; // nw diagonal line
+		map[L'\u2571'] = 0xce; // ne diagonal line
+		map[L'\u25cf'] = 0xd1; // filled circle
+		map[L'\u2665'] = 0xd3; // heart
+		map[L'\u2573'] = 0xd6; // cross
+		map[L'\u25cb'] = 0xd7; // circle
+		map[L'\u2663'] = 0xd8; // club
+		map[L'\u2666'] = 0xda; // diamond
+		map[L'\u254b'] = 0xdb; // junction
+		map[L'\u2503'] = 0xdd; // vertical line
<