biojppm/rapidyaml

Meaning of .val()==nullptr and .has_val()

gczuczy opened this issue · 2 comments

Hello,

I've got the following input:

{'fermenter': null, 'id': 1}

and the following code for it:

          std::cout << _node << std::endl;
          ryml::ConstNodeRef f = _node["fermenter"];
          std::cout << f << std::endl;
          printf("fermenter isnull: %c\n", (f.val() ==nullptr)?'t':'f');
          printf("fermenter hasval: %c\n", (f.has_val())?'t':'f');

Which prints:

data:
  'fermenter': null
  'id': 1

'fermenter': null

fermenter isnull: f
fermenter hasval: t

It's on tag v0.5.0, and I'm trying to check for null values based on

// reading empty/null values - see also sample_formatting()

Could you please adjust the examples? Btw val_is_null is working as expected.

When the input has value, the output is:

data:
  'fermenter':
    'id': '1'
  'id': 1

fermenter:
  'id': '1'
Abort trap (core dumped)

Segfaults are the first printf. val_is_null() also segfaults here.

Thanks

This is a continuation of #409.

All the behavior you describe is intended.

You seem to be laboring under a misunderstanding about what these functions do.

  • .has_val() is purely a predicate to check if a node has a (possibly empty) scalar as its value. .has_val() is a structural query, and the logical opposite of .is_container() in YAML structure; a container cannot be a val(scalar), and a val(scalar) cannot be a container. It tells you nothing about the contents of the scalar itself; only that it has a scalar and is therefore not a container. For example, in the YAML {a: } for node root["a"], .has_val() is true, even though its val is set to {nullptr, 0}. But it is set, and this is the intent of the query.
  • when a node does not have a val, using .val() to get the val is UB. It causes an assertion in debug builds and gives you garbage in optimized builds.
  • likewise, if you ask whether .val_is_null() you will get the same assertion because
    1. this check involves a string comparison against "null", "~", "Null", etc
    2. to get the val string, it needs to exist
    3. the node .is_container(), and does not have a val, so you can't read the val
    4. hence the assertion you get when calling .val_is_null() when the node is a container

As for nullity, in the first example:

  • f has the string "null" as its scalar value
  • so it .has_val()
  • the val is a non-empty string, and has address. So comparing with nullptr yields false, because it is pointing at the string "null" which is non-null
  • .val_is_null() is indeed what you seem to be looking for. This function gets the scalar, and checks its contents against the preset list of null-meaning strings in YAML. But again, the subject node needs to verify .has_val(), ie the subject node cannot be a container.

Consider this:

const Tree tree = parse_in_arena(R"(
a:
b: null
c: ~
)");
ConstNodeRef root = tree.rootref();
ConstNodeRef a = root["a"];
ConstNodeRef b = root["b"];
ConstNodeRef c = root["c"];
// the following holds:
assert(root.is_map());
assert(root.is_container());
assert(!root.has_val());
assert(a.has_val());
assert(b.has_val());
assert(c.has_val());
assert(a.val() == "");
assert(b.val() == "null");
assert(c.val() == "~");
assert(a.val() == nullptr);
assert(b.val() != nullptr); 
assert(c.val() != nullptr);
assert(a.val_is_null());
assert(b.val_is_null());
assert(c.val_is_null());
assert(!root.val_is_null()); // ERROR! UB / crash. root is a container, cannot read the val.

So what you want to do is something like this:

// ...
ConstNodeRef f = _node["fermenter"]; // but ensure that "fermenter" exists!
if(f.is_container())  {
    csubstr id = f["id"].val(); // but ensure that "id" exists!
}
else if( ! f.val_is_null()) {
    csubstr id = f.val();
}
else {
     // how to get the id from null?
}

Got it, thank you. Sorry about not understanding the logic, the samples are not explaining in, and without a documentation that's explaining these things like you do it here, it's mostly trial and error.