Using enum with std.son.JSONValue - d

How can enum be used with std.json.JSONValue in D? The implementing code does not have access to the module types and to! cannot be used, that's the reason for the CommonType!(OriginalType!()) call.
import std.json;
import std.stdio;
import std.traits;
enum Kind : string
{
dog = "Dog",
cat = "Cat",
pony = "Pony"
};
unittest
{
writeln("TEST: std.json.JSONValue for enum");
Kind animal = Kind.dog;
Kind[] pets = [Kind.cat, Kind.dog];
static if (is(typeof(animal) == enum))
{
writeln("enum");
}
//JSONValue a = JSONValue(animal); // <-- Assertion failed: (0), function unqualify, file mtype.c, line 2022.
JSONValue a = JSONValue(cast(CommonType!(OriginalType!(typeof(animal))))animal); // <-- this is OK
static if (is(typeof(pets) == enum))
{
writeln("array of enum");
}
//JSONValue p = JSONValue(pets); // <-- Assertion failed: (0), function unqualify, file mtype.c, line 2022.
//JSONValue p = JSONValue(???);
}

You can use convert your enum values to strings and back using std.conv.to.
import std.json;
import std.conv : to;
import std.algorithm : map, equal;
...
Kind animal = Kind.dog;
Kind[] pets = [Kind.cat, Kind.dog];
// convert Kind to string and store in JSONValue
JSONValue json1 = animal.to!string;
// extract string from JSONValue convert to Kind
assert(animal == json1.str.to!Kind);
JSONValue json2 = pets.to!(string[]);
// map JSONValues to strings, then Kinds
auto pets2 = json2.array.map!(x => x.str.to!Kind);
assert(pets2.equal(pets));
Using string, the JSON values would be represented as strings matching the enum names. You could instead use int or anything else that can be converted to both your enum type and a JSONValue.

Related

How to programmatically assign class/struct attributes?

I am attempting to translate my Python program to C++, but because I am new to C++ I am encountering some problems. The input file is first parsed (works, not shown) to create the INITIAL_VALUES dict/map, which I then want to use to assign the Parameters class/struct attributes using the DEST_DICT_PARAMS dict/map.
I was able to achieve this in Python code with:
import dataclasses
INITIAL_VALUES = {
"BULK": {
"MAGMA": {
"M0": 1.0,
"T0": 1320.0,
},
"ASSIM": {
"M0": 0.0,
"T0": 600.0,
},
}
}
DEST_DICT_PARAMS = {
'M0': {"MAGMA": 'Mm0', "ASSIM": 'Ma0'},
'T0': {"MAGMA": 'Tm0', "ASSIM": 'Ta0'},
}
#dataclasses.dataclass
class Parameters:
Mm0: float = None
Ma0: float = None
Ta0: float = None
Tm0: float = None
class ParametersReader:
def __init__(self):
self.parameters = Parameters()
self._assignParameters()
def _assignParameters(self):
for param_fam, dest in DEST_DICT_PARAMS.items():
for component, param in dest.items():
value = INITIAL_VALUES["BULK"][component][param_fam]
setattr(self.parameters, param, value)
params = ParametersReader()
print(params.parameters)
Output:
Parameters(Mm0=1.0, Ma0=0.0, Ta0=600.0, Tm0=1320.0)
So I wrote the corresponding C++ code:
#include <iostream>
#include <map>
using std::map;
using std::string;
map<string, map<string, map<string, float> > > INITIAL_VALUES = {{
"BULK", {
{"MAGMA", {
{"M0", 1.0},
{"T0", 1320.0},
}},
{"ASSIM", {
{"M0", 0.0},
{"T0", 600.0},
}},
}
}};
map<string, map<string, string> > DEST_DICT_PARAMS = {{
{"M0", {{"MAGMA", "Mm0"}, {"ASSIM", "Ma0"}}},
{"T0", {{"MAGMA", "Tm0"}, {"ASSIM", "Ta0"}}},
}};
struct Parameters {
float Mm0;
float Ma0;
float Ta0;
float Tm0;
} parameters;
class ParametersReader {
public:
void assignParameters_() {
map<string, map<string, string> >::iterator itr0;
map<string, string>::iterator itr1;
for (itr0 = DEST_DICT_PARAMS.begin(); itr0 != DEST_DICT_PARAMS.end(); itr0++) {
for (itr1 = itr0->second.begin(); itr1 != itr0->second.end(); itr1++) {
parameters.itr1->second = INITIAL_VALUES["BULK"][itr1->first];
}
}
}
};
int main() {
ParametersReader params;
params.assignParameters_();
}
But I'm getting an error at the line
parameters.itr1->second = INITIAL_VALUES['BULK'][itr1->first] saying "no member named 'itr1' in 'Parameters'". That error makes total sense because the code is literally trying to interpret 'itr1' as an attribute name and not the whole 'itr1->second' as the name. I think this comes down to the fact that I can't seem to find a C++ equivalent to Python's setattr(obj, name, val) function that takes an object and its attribute name and assigns it a value. Is there a C++ solution to what I am attempting?
Perhaps my entire approach is incompatible with C++. If so, would you kindly suggest an alternative approach? I would like to keep the input file format the same between the Python and C++ versions.
C++ does not have runtime reflection like Python. You cannot look up a class member by name using a runtime string because class member names do not exist at runtime.
What you can do is look up a class member via a pointer to member. This is an offset into the object calculated at compile time by the compiler:
std::map<std::string, std::map<std::string, float Parameters::*> > DEST_DICT_PARAMS = {{
{"M0", {{"MAGMA", &Parameters::Mm0}, {"ASSIM", &Parameters::Ma0}}},
{"T0", {{"MAGMA", &Parameters::Tm0}, {"ASSIM", &Parameters::Ta0}}},
}};
class ParametersReader {
public:
void assignParameters_() {
for (auto& [param_fam, dest] : DEST_DICT_PARAMS) {
for (auto& [component, param] : dest) {
parameters.*param = INITIAL_VALUES["BULK"][component][param_fam];
}
}
}
};
Demo
Note I've also used range-based for loops and structured bindings to clean up your assignParameters_ function.
C++ has no equivalent to Pythons setattr(self.parameters, param, value). If you want to have a mapping between strings and members you need to write it yourself.
You can use pointers to members to do something along the line of:
#include <map>
#include <iostream>
struct foo {
float a = 0.0f;
float b = 0.0f;
};
// list all members and their string represenation
std::map<std::string,float foo::*> mmap {
{ "a", &foo::a },
{ "b", &foo::b }
};
int main() {
foo f;
std::map<std::string,float> values {
{"a", 0.1},
{"b", 0.2}
};
for (const auto& val : values) {
// lookup the member pointers via the strings from mmap
// use the found function pointer to assing to corresponding member in f
f.*mmap[val.first] = val.second;
}
std::cout << f.a << " " << f.b << "\n";
}
Note that the code assumes that all strings present in values are also present in mmap. If not, it will fail horribly. To fix that mmap.find should be used instead and the case of not found string handleted appropriately.
This works, though there is no way to get that mapping implicitly from the class definition only. On the other, hand I can imagine libraries to exist that can help with that.

EMF: Defining a generic containment reference in an Ecore metamodel

It is a long time since I have used EMF and I am stuck on this.
I would like to create a generic type equivalent to:
class Result<T:ASTNode>{
T root;
}
I am defining this in Kotlin:
val result = ePackage.createEClass("Result").apply {
// I think this part is correct
val typeParameter = EcoreFactory.eINSTANCE.createETypeParameter().apply {
this.name = "T"
this.eBounds.add(EcoreFactory.eINSTANCE.createEGenericType().apply {
// astNode is my EClass
this.eClassifier = astNode
})
}
this.eTypeParameters.add(typeParameter)
val rootContainment = EcoreFactory.eINSTANCE.createEReference()
rootContainment.name = "root"
// STUCK!
// here should I set rootContainment.eType? rootContainment.eGenericType?
// how?
rootContainment.isContainment = true
rootContainment.lowerBound = 0
rootContainment.upperBound = 1
this.eStructuralFeatures.add(rootContainment)
addContainment("issues", issue, 0, -1)
}
The equivalent .ecore is :
<eClassifiers xsi:type="ecore:EClass" name="Result">
<eTypeParameters name="T">
<eBounds eClassifier="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eTypeParameters>
<eStructuralFeatures xsi:type="ecore:EReference" name="t">
<eGenericType eTypeParameter="#//Result/T"/>
</eStructuralFeatures>
so you want to use rootContainment.eGenericType with a new EGenericType that references your ETypeParameter

Adding a custom type to a RedBlackTree

I want to keep an ordered set of records and the standard provides me with RedBlackTree. The record is of type Tuple!(string, uint). Here's what it looks like:
import std.json : parseJSON;
uint[string] wordTable;
import std.datetime.stopwatch : StopWatch, AutoStart;
auto sw = StopWatch(AutoStart.yes);
const auto j = parseJSON(get(link));
const long downloadTime = sw.peek.total!"msecs";
import std.typecons : Tuple, tuple;
import std.container.rbtree : RedBlackTree;
import std.functional : binaryFun;
RedBlackTree!(Tuple!(string, uint), binaryFun!("a[1] > b[1]")) records;
foreach (node; j["posts"].array()) {
import std.stdio : writeln;
import std.utf : decode;
if ("com" in node) {
import std.algorithm : splitter;
foreach (word; getStr(node["com"].str()).splitter(' ')) {
import std.string : strip;
if (word.strip().length > 0)
wordTable.require(word, 0)++;
records ~= (tuple(word, wordTable[word])); // error
}
}
}
Now primarily I had used insert() method to add a record to the records but it causes segfault in runtime. So I decided to use ~= in hopes for better error messages. And here's what the compiler says:
Error: cannot append type Tuple!(string, uint) to type std.container.rbtree.RedBlackTree!(Tuple!(string, uint), binaryFun, false)
According to https://dlang.org/phobos/std_container_rbtree.html#.RedBlackTree I have to provide a type such that calling less(a, b) on it returns a boolean. So I went ahead and created a type for it:
struct Record {
string key;
uint value;
int opCmp(ref const Record other) const {
return value - other.value;
}
}
// bool less(Record a, Record b) {
// return a < b;
// }
void main(string[] args) {
import std.stdio : writeln, writefln;
if (args.length < 3) {
writeln("Must have 2 arguments " ~ "first argument is the link, "
~ "the second one is for minimum repeatation threshold. Exiting.");
import core.stdc.stdlib : exit;
exit(-1);
}
const auto link = parseLink(args[1]);
const auto threshold = atoui(args[2]);
import std.json : parseJSON;
uint[string] wordTable;
import std.datetime.stopwatch : StopWatch, AutoStart;
auto sw = StopWatch(AutoStart.yes);
const auto j = parseJSON(get(link));
const long downloadTime = sw.peek.total!"msecs";
import std.container.rbtree : RedBlackTree;
import std.functional : binaryFun;
RedBlackTree!Record records;
foreach (node; j["posts"].array()) {
import std.utf : decode;
if ("com" in node) {
import std.algorithm : splitter;
foreach (word; getStr(node["com"].str()).splitter(' ')) {
import std.string : strip;
if (word.strip().length > 0)
wordTable.require(word, 0)++;
records ~= (Record(word, wordTable[word]));
}
}
}
This time the compiler complains:
Error: cannot append type Record to type std.container.rbtree.RedBlackTree!(Record, "a < b", false)
So the gist of the question is, if I have an RedBlackTree with a custom binaryFun, how can I add an instance of a tuple or a custom type to it?

D - static if on variadic arguments not working properly

Suppose I have the following variadic function, which job is to concat together a path from pieces (each piece can either be an index of integral type, or a node of string type):
string
makePath(P...)(P path)
{
import std.conv;
import std.format;
string res;
foreach (piece; path) {
pragma(msg, typeof(piece).stringof);
static if (is(piece: size_t))
res = res is null ? piece.to!string : format("%s[%s]", res, piece);
else if (is(piece: string))
res = res is null ? piece : format("%s.%s", res, piece);
else
static assert(0);
}
}
If I later use it like this: string path = makePath("foo", "bar"), somehow the code reaches the static assert(0); and the compilation terminates. This is most curious, but the pragma actually writes string as the type of the first argument despite the fact that a code path for some other type was taken.
Even better, using makePath(12, 13) results in the compiler complaining about both the line for strings (about incompatible types of int and string) and static assert. What is going on here?
I've tried this both on DMD and LDC.
The is keyword is what's at fault here. (It's quite a confusing keyword I find...)
I'd recommend you use the templates in std.traits to test for types, most there are covered: https://dlang.org/phobos/std_traits.html
Here's a working version of your function:
string
makePath(P...)(P path)
{
import std.conv;
import std.format;
import std.traits : isSomeString, isNumeric;
string res;
foreach (piece; path) {
static if (isNumeric!(typeof(piece)))
res = res is null ? piece.to!string : format("%s[%s]", res, piece);
else static if (isSomeString!(typeof(piece))) // Note, you were missing the 'static' on the 'else if' part
res = res is null ? piece : format("%s.%s", res, piece);
else
static assert(0);
}
return res;
}
unittest {
static assert(makePath("foo","bar") == "foo.bar");
static assert(makePath("foo","bar",1) == "foo.bar[1]");
}

How to append an attribute-value pair on an existing json11 object (c++)?

For example,
I'm building a json message using following code:
json11::Json my_json = json11::Json::object{
{ "key_val1", val1},
{ "key_val2", val2},
{ "key_val3", val3},
{ "key_val4", val4 }
};
std::string message = my_json.dump();
But if i want to have this json11 object contain different attribute-value pair based on some condition then I've to repeat the same code multiple times.
Is there any way to append attribute-value pair to an existing json11 object?
So that i can build a base object and then append necessary attributes on demand.
Yes it's possible.
json11::Json::object my_json = json11::Json::object{
{ "key_val1", val1},
{ "key_val2", val2},
{ "key_val3", val3},
{ "key_val4", val4 }
};
my_json["newattribute1"] = "newValue1";
my_json["newattribute2"] = 2;
json11::Json json_final = json11::Json{ my_json };
std::string message = json_final .dump();
In your case my_json is an instance of json11::Json. In my case my_json is an instance of json11::Json::object.
json11::Json::object is originally a std::map.