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]|<<</code></td><td>left shift</td><td>integer</td><td><code>[6502]|aa << 1</code></td></tr>
- <tr><td><code>[6502]|>></code></td><td>right shift</td><td>integer</td><td><code>[6502]|aa >> 1</code></td></tr>
+ <tr><td><code>[6502]|<<</code></td><td>logical left shift</td><td>integer</td><td><code>[6502]|aa << 1</code></td></tr>
+ <tr><td><code>[6502]|>></code></td><td>logical right shift</td><td>integer</td><td><code>[6502]|aa >> 1</code></td></tr>
+ <tr><td><code>[6502]|>>></code></td><td>arithmetic right shift</td><td>integer</td><td><code>[6502]|aa >>> 1</code></td></tr>
<tr><td><code>[6502]|&</code></td><td>bitwise and</td><td>integer</td><td><code>[6502]|aa & 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]|&&</code></td><td>boolean and</td><td>boolean</td><td><code>[6502]|aa && 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]|&=</code></td><td>bitwise and, and assign</td><td>integer</td><td><code>[6502]|aa &= 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]|<<=</code></td><td>left shift, and assign</td><td>integer</td><td><code>[6502]|aa <<= 1</code></td></tr>
- <tr><td><code>[6502]|>>=</code></td><td>right shift, and assign</td><td>integer</td><td><code>[6502]|aa >>= 1</code></td></tr>
+ <tr><td><code>[6502]|<<=</code></td><td>logical left shift, and assign</td><td>integer</td><td><code>[6502]|aa <<= 1</code></td></tr>
+ <tr><td><code>[6502]|>>=</code></td><td>logical right shift, and assign</td><td>integer</td><td><code>[6502]|aa >>= 1</code></td></tr>
+ <tr><td><code>[6502]|>>>=</code></td><td>arithmetic right shift, and assign</td><td>integer</td><td><code>[6502]|aa >>>= 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
<