M core/core/json/json_type.cpp +12 -0
@@ 142,6 142,12 @@ namespace core { namespace json {
return (*_data.a)[index];
}
+ void JsonType::move_push_back(JsonType &&type)
+ {
+ assert(is_array()); // expected array
+ _data.a->push_back(std::move(type));
+ }
+
// map methods
JsonType::MapType::const_iterator JsonType::map_begin() const
@@ 186,6 192,12 @@ namespace core { namespace json {
return (*_data.o).find(std::wstring(key)) != _data.o->end();
}
+ void JsonType::move_insert(std::wstring_view key, JsonType &&type)
+ {
+ assert(is_object()); // expected object
+ (*_data.o)[std::wstring(key)] = std::move(type);
+ }
+
// array or map methods
size_t JsonType::size() const
M core/core/json/json_type.h +45 -1
@@ 2,6 2,7 @@
#include <map>
#include <string>
+#include <string_view>
#include <vector>
namespace core {
@@ 27,6 28,9 @@ namespace core {
Integer,
};
+ class ArrayToken {};
+ class ObjectToken {};
+
/// \class JsonType
/// \brief This is the class of all JSON types.
class JsonType
@@ 44,6 48,44 @@ namespace core {
: _type_id(TypeID::None)
{}
+ explicit JsonType(bool value)
+ : _type_id(TypeID::Bool)
+ {
+ _data.b = value;
+ }
+
+ explicit JsonType(int32_t value)
+ : _type_id(TypeID::Number)
+ , _number_type(NumberType::Integer)
+ {
+ _data.i = value;
+ }
+
+ explicit JsonType(float value)
+ : _type_id(TypeID::Number)
+ , _number_type(NumberType::Float)
+ {
+ _data.f = value;
+ }
+
+ JsonType(std::wstring_view value)
+ : _type_id(TypeID::String)
+ {
+ _data.s = new std::wstring(value);
+ }
+
+ explicit JsonType(ArrayToken)
+ : _type_id(TypeID::Array)
+ {
+ _data.a = new ArrayType();
+ }
+
+ explicit JsonType(ObjectToken)
+ : _type_id(TypeID::Object)
+ {
+ _data.o = new MapType();
+ }
+
~JsonType() {
clear();
}
@@ 78,6 120,7 @@ namespace core {
iterator end();
const JsonType &operator[](size_t index) const;
JsonType &operator[](size_t index);
+ void move_push_back(JsonType &&type);
// map methods
@@ 88,6 131,7 @@ namespace core {
const JsonType &operator[](const wchar_t *key) const;
JsonType &operator[](const wchar_t *key);
bool has(std::wstring_view key) const;
+ void move_insert(std::wstring_view key, JsonType &&type);
// array or map methods
@@ 102,7 146,7 @@ namespace core {
union {
bool b;
- int i;
+ int32_t i;
float f;
std::wstring *s;
ArrayType *a;
A => core/core/json/json_writer.cpp +94 -0
@@ 0,0 1,94 @@
+#include "pch.h"
+
+#include <core/json/json_writer.h>
+#include <format>
+#include <sstream>
+
+namespace core {
+namespace json {
+
+ void indent_line(std::basic_ostringstream<wchar_t> &output, int32_t indent)
+ {
+ if (indent < 0) {
+ return;
+ }
+ for(int32_t i = 0; i < indent; ++i) {
+ output << L'\t';
+ }
+ }
+
+ void to_string(const JsonType &value, std::basic_ostringstream<wchar_t> &output, int32_t indent)
+ {
+ if (value.is_int()) {
+ output << std::format(L"{}", value.as_int());
+
+ } else if (value.is_float()) {
+ output << std::format(L"{}", value.as_float());
+
+ } else if (value.is_string()) {
+ output << L'"' << value.as_string() << L'"';
+
+ } else if (value.is_array()) {
+ output << L'[';
+ if (indent >= 0)
+ output << L'\n';
+ if (indent >= 0)
+ ++indent;
+ for(JsonType::const_iterator it = value.begin(); it != value.end(); ++it) {
+ const JsonType &item = *it;
+ indent_line(output, indent);
+ to_string(item, output, indent);
+ if (it + 1 != value.end()) {
+ output << L',';
+ }
+ if (indent >= 0)
+ output << L'\n';
+ }
+ if (indent >= 0)
+ --indent;
+ indent_line(output, indent);
+ output << L']';
+
+ } else if (value.is_object()) {
+ output << L'{';
+ if (indent >= 0)
+ output << L'\n';
+ if (indent >= 0)
+ ++indent;
+ for(JsonType::MapType::const_iterator it = value.map_begin(); it != value.map_end(); ) {
+ const JsonType::MapType::value_type &pair = *it;
+ indent_line(output, indent);
+ output << L'"';
+ output << pair.first;
+ output << L"\":";
+ if (indent >= 0)
+ output << L' ';
+ to_string(pair.second, output, indent);
+ if (++it != value.map_end()) {
+ output << L',';
+ }
+ if (indent >= 0)
+ output << L'\n';
+ }
+ if (indent >= 0)
+ --indent;
+ indent_line(output, indent);
+ output << L'}';
+
+ } else if (value.is_bool()) {
+ output << (value.as_bool() ? L"true" : L"false");
+
+ } else {
+ output << L"none";
+ }
+ }
+
+ void to_string(const JsonType &type, std::wstring &output, bool human_readable)
+ {
+ std::wostringstream ss;
+ to_string(type, ss, human_readable ? 0 : -1);
+ output = ss.str();
+ }
+
+}
+}
A => core/core/json/json_writer.h +12 -0
@@ 0,0 1,12 @@
+#pragma once
+
+#include <core/json/json_type.h>
+#include <sstream>
+
+namespace core {
+namespace json {
+
+void to_string(const JsonType &value, std::wstring &output, bool human_readable);
+
+}
+}
M jasm/assemble/assembler_impl/assembler_impl.h +2 -0
@@ 693,7 693,9 @@ private:
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 function_json_write(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1, uint32_t arg2, uint32_t arg3);
+ core::json::JsonType export_json_value(const Value &value, AssemblyErrorCodes &error_code, std::string &error);
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);
M jasm/assemble/assembler_impl/functions_impl.cpp +124 -1
@@ 4,7 4,10 @@
#include <assemble/functions.h>
#include <cmath>
#include <core/environment/log.h>
+#include <core/io/file_helpers.h>
+#include <core/io/file_writer.h>
#include <core/json/json_parser.h>
+#include <core/json/json_writer.h>
#include <core/math/sign.h>
#include <core/strings/string_helpers.h>
#include <core/strings/utf8.h>
@@ 64,6 67,7 @@ Function Assembler::function_pointer(Fun
{ &Assembler::function_symbol },
{ &Assembler::function_select },
{ &Assembler::function_json_read },
+ { &Assembler::function_json_write },
};
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);
@@ 1567,13 1571,132 @@ void Assembler::function_json_read(bool
import_json_object(object, contents);
}
+void Assembler::function_json_write(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1, uint32_t arg2, uint32_t arg3)
+{
+ if (!generate) {
+ return;
+ }
+
+ const Value &expr1_value = follow_reference_or_value(expression_values[arg1]);
+ if (!is_string(expr1_value)) {
+ const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg1)];
+ std::stringstream ss;
+ ss << "Function needs string expression argument but got " << to_string(expr1_value.type) << ".";
+ report_error(ec.source_location, AssemblyErrorCodes::ExpectedStringArgument, ss.str());
+ return;
+ }
+
+ const Value &expr2_value = follow_reference_or_value(expression_values[arg2]);
+ if (!is_dict(expr2_value)) {
+ const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg2)];
+ std::stringstream ss;
+ ss << "Function needs dict expression argument but got " << to_string(expr2_value.type) << ".";
+ report_error(ec.source_location, AssemblyErrorCodes::ExpectedDictArgument, ss.str());
+ return;
+ }
+
+ const Value &expr3_value = follow_reference_or_value(expression_values[arg3]);
+ if (!is_boolean(expr3_value)) {
+ const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg3)];
+ std::stringstream ss;
+ ss << "Function needs boolean expression argument but got " << to_string(expr3_value.type) << ".";
+ report_error(ec.source_location, AssemblyErrorCodes::ExpectedBooleanArgument, ss.str());
+ return;
+ }
+
+ // convert to json tree
+ AssemblyErrorCodes error_code = AssemblyErrorCodes::Ok;
+ std::string error;
+ core::json::JsonType object = export_json_value(expr2_value, error_code, error);
+ if (error_code != AssemblyErrorCodes::Ok)
+ {
+ const ExpressionComponent &ec = components[leftmost_node_in_expression(components, arg2)];
+ report_error(ec.source_location, error_code, error);
+ return;
+ }
+
+ // convert tree to string
+ std::wstring wide_json;
+ bool indent = dereference_boolean(expr3_value);
+ core::json::to_string(object, wide_json, indent);
+
+ // save string
+ std::string_view filename = dereference_string(expr1_value);
+ std::string utf8_json = core::wide_to_utf8(wide_json);
+
+ if (!core::save_file(std::string(filename), utf8_json, error)) {
+ const ExpressionComponent &ec = components[op];
+ std::stringstream ss;
+ ss << "Failed to write json file: " << error;
+ report_error(ec.source_location, AssemblyErrorCodes::IOError, ss.str());
+ }
+}
+
+core::json::JsonType Assembler::export_json_value(const Value &value, AssemblyErrorCodes &error_code, std::string &error)
+{
+ core::json::JsonType type;
+
+ if (is_boolean(value)) {
+ type = core::json::JsonType(dereference_boolean(value));
+
+ } else if (is_integer(value)) {
+ type = core::json::JsonType(dereference_integer(value));
+
+ } else if (is_float(value)) {
+ type = core::json::JsonType(static_cast<float>(dereference_float(value)));
+
+ } else if (is_string(value)) {
+ type = core::json::JsonType(utf8_to_wide(dereference_string(value)));
+
+ } else if (is_list(value)) {
+ const ListDynamicObject &list_object = value.object_reference.deref<ListDynamicObject>();
+ const std::vector<Value> &values = *list_object.value;
+ type = core::json::JsonType(core::json::ArrayToken());
+ for(const Value &v : values) {
+ type.move_push_back(export_json_value(v, error_code, error));
+ if (error_code != AssemblyErrorCodes::Ok) {
+ return core::json::JsonType();
+ }
+ }
+
+ } else if (is_dict(value)) {
+ const DictDynamicObject &dict_object = value.object_reference.deref<DictDynamicObject>();
+ const DictDynamicObject::DictType &values = *dict_object.value;
+ type = core::json::JsonType(core::json::ObjectToken());
+ for(const auto &pair : values) {
+ if (!is_string(pair.second.first)) {
+ std::stringstream ss;
+ ss << "Unsupported type " << to_string(pair.second.first.type) << " as key in json object.";
+ error_code = AssemblyErrorCodes::UnsupportedTypeForJsonExport;
+ error = ss.str();
+ return core::json::JsonType();
+ }
+
+ core::json::JsonType value_type = export_json_value(pair.second.second, error_code, error);
+ if (error_code != AssemblyErrorCodes::Ok) {
+ return core::json::JsonType();
+ }
+
+ type.move_insert(utf8_to_wide(dereference_string(pair.second.first)), std::move(value_type));
+ }
+
+ } else {
+ std::stringstream ss;
+ ss << "Unsupported type " << to_string(value.type) << " for json write.";
+ error_code = AssemblyErrorCodes::UnsupportedTypeForJsonExport;
+ error = ss.str();
+ }
+
+ return type;
+}
+
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());
+ set_float(value, double(json_value.as_float()));
} else if (json_value.is_string()) {
std::string value_string = core::wide_to_utf8(json_value.as_string());
M jasm/assemble/functions.cpp +2 -0
@@ 57,6 57,7 @@ const FunctionDesc &function_info(Functi
{not_lazy, fixed_args, 1}, // symbol
{is_lazy, fixed_args, 3}, // select
{not_lazy, fixed_args, 1}, // json_read
+ {not_lazy, fixed_args, 3}, // json_write
};
static_assert(sizeof(desc) / sizeof(desc[0]) == static_cast<int>(FunctionType::NumTypes), "Number of functions doesn't match number of decl structs");
@@ 112,6 113,7 @@ std::string_view to_string(FunctionType
std::string_view("symbol"),
std::string_view("select"),
std::string_view("json_read"),
+ std::string_view("json_write"),
};
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
@@ 52,6 52,7 @@ enum class FunctionType : uint8_t
Symbol,
Select,
JsonRead,
+ JsonWrite,
NumTypes,
};
M jasm/exceptions/error_codes.h +3 -0
@@ 212,6 212,9 @@ enum class AssemblyErrorCodes
BreakWithoutLoop,
FailedToIncludeJson,
JsonParseError,
+ ExpectedDictArgument,
+ UnsupportedTypeForJsonExport,
+ IOError,
// assembler warnings
SubroutineFallthrough = 3500,
A => jasm/unit_tests/results/test_json_export_filename_must_be_string.stdout +2 -0
@@ 0,0 1,2 @@
+unit_tests/test_json_export_filename_must_be_string.asm(6,13) : Error 3048 : Function needs string expression argument but got integer.
+Assembly ended with errors.
A => jasm/unit_tests/results/test_json_export_indent_arg_must_be_boolean.stdout +2 -0
@@ 0,0 1,2 @@
+unit_tests/test_json_export_indent_arg_must_be_boolean.asm(6,32) : Error 3049 : Function needs boolean expression argument but got integer.
+Assembly ended with errors.
A => jasm/unit_tests/results/test_json_export_type_must_be_dict.stdout +2 -0
@@ 0,0 1,2 @@
+unit_tests/test_json_export_type_must_be_dict.asm(6,26) : Error 3121 : Function needs dict expression argument but got boolean.
+Assembly ended with errors.
A => jasm/unit_tests/results/test_json_export_unsupported_type.stdout +2 -0
@@ 0,0 1,2 @@
+unit_tests/test_json_export_unsupported_type.asm(7,17) : Error 3122 : Unsupported type macro for json write.
+Assembly ended with errors.
A => jasm/unit_tests/results/test_json_export_write_error.stdout +2 -0
@@ 0,0 1,2 @@
+unit_tests/test_json_export_write_error.asm(6,12) : Error 3123 : Failed to write json file: Failed to open '' for writing
+Assembly ended with errors.
A => jasm/unit_tests/test_json_export_filename_must_be_string.asm +7 -0
@@ 0,0 1,7 @@
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", $8000
+{
+ const test = dict()
+ json_write(0, test, true)
+}
A => jasm/unit_tests/test_json_export_indent_arg_must_be_boolean.asm +7 -0
@@ 0,0 1,7 @@
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", $8000
+{
+ const test = dict()
+ json_write("test.json", test, 0)
+}
A => jasm/unit_tests/test_json_export_type_must_be_dict.asm +7 -0
@@ 0,0 1,7 @@
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", $8000
+{
+ const test = true
+ json_write("test.json", test, true)
+}
A => jasm/unit_tests/test_json_export_unsupported_type.asm +8 -0
@@ 0,0 1,8 @@
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", $8000
+{
+ macro m() { }
+ const test = dict("macro" = m)
+ json_write("", test, true)
+}
A => jasm/unit_tests/test_json_export_write_error.asm +7 -0
@@ 0,0 1,7 @@
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", $8000
+{
+ const test = dict()
+ json_write("", test, true)
+}