<JordanBrown1>
C++ question (for OpenSCAD internals)...
<JordanBrown1>
I know that you can have a function "Value& foo()" that returns a Value without copying it (somehow; the details remain a mystery to me).
<JordanBrown1>
Right?
<JordanBrown1>
Now, is there a way to instead return that Value via an argument, again without copying it?
<JordanBrown1>
What I'm looking for is some way to say something like "void foo(Value& v)" that will return the Value into that argument without copying it, in the same way that return can.
<InPhase>
JordanBrown1: Maybe passing in a reference to a reference_wrapper?
<JordanBrown1>
That sounds ... complicated. It seems like there should be something as simple as the return case.
<InPhase>
JordanBrown1: There's a bit of code smell warning to even suggesting it. But sometimes weird things are necessary. :)
<JordanBrown1>
In C, I'd just have the argument be a Value** and set it to a Value*, but that doesn't seem to be the C++ way.
<InPhase>
Yeah, reference to reference_wrapper is basically the equivalent to Value**, because you need the ability to assign to an existing external lifetime object.
<JordanBrown1>
*Maybe* the answer is that I need to do the assignment as "v = std::move(...)"
<InPhase>
But it could easily just be a case of an architecture being wrong.
<JordanBrown1>
but I don't really understand std::move and so don't know whether that will do something bad to the original.
<InPhase>
std::move by itself does absolutely nothing. It's a typecast. Only when you assign it, does it invalidate the object moved from.
<JordanBrown1>
If it wasn't so easy to do it via the return value I'd think that I was just looking in the wrong place.
<JordanBrown1>
But it's easy (ish :-) to do on return, so it seems like it might be easy to do via a parameter.
<InPhase>
To understand std::move, recognize that it's named wrong, and should have been std::movable
<JordanBrown1>
And, this being C++, I might well have just not found exactly the right incantation.
<InPhase>
Maybe xy problem unwrap it? What's the more specific problem?
<JordanBrown1>
I'm frustrated by inconsistent argument checking for builtin functions and modules, and so am trying to design the One True Argument Checker And Retriever.
<JordanBrown1>
The pattern that I would like is
<JordanBrown1>
... er, backing up a little ...
<JordanBrown1>
The usual consumer pattern I would like to be
<JordanBrown1>
So you call it, and it either gives you a double and returns true, or returns false.
<JordanBrown1>
and if it returns false, it also yields a message (which might well throw).
<JordanBrown1>
The reason it needs to return the bool is so that the caller can plow on (probably returning undef) if hardwarnings is off.
<JordanBrown1>
The reason for taking the return value as an argument is so that there can be a whole family of these, that know what type they are to retrieve by what you pass to them.
<JordanBrown1>
I thought a bit about boost::optional<double> required(const std::string& name)
<JordanBrown1>
but then you have to have a different function name for each type.
<InPhase>
How does the caller know what type to request?
<JordanBrown1>
Remember that this is for internal functions and modules.
<JordanBrown1>
It knows what type it wants.
<JordanBrown1>
sin() wants a number.
J25K57 has joined #openscad
<InPhase>
Many built-ins take different types for the same parameter. How is that handled?
<JordanBrown1>
Haven't gotten there yet.
<JordanBrown1>
They will undoubtedly need to do something more complicated.
<JordanBrown1>
But I want the simple cases to be simple.
<JordanBrown1>
So an obvious lower-level function would be one that looks something like
<InPhase>
So this "required" should check to see whether the built-in is used wrongly if it's missing a specific argument?
<JordanBrown1>
yes
<InPhase>
Is this to be at an earlier stage or something?
<JordanBrown1>
No, in the C++ function that implements the OpenSCAD function.
<InPhase>
So why is a required function required (or helpful) for this?
J25K98 has quit [Ping timeout: 250 seconds]
<InPhase>
Each function already knows what's required.
<InPhase>
Is this like an assert? "This is required, so raise hell if it's missing"?
<JordanBrown1>
Yes.
<InPhase>
Ok.
<JordanBrown1>
Hmm. sin() may not be a good example, because it already has a check_arguments() that on its face looks adequate.
<JordanBrown1>
But it doesn't handle *named* arguments.
<InPhase>
And how are positional vs named handled, or combinations of these?
<JordanBrown1>
There's an existing Parameters::parse() that does that.
<JordanBrown1>
But it doesn't do type checking, and doesn't do missing-argument errors.
<JordanBrown1>
One of the things that's frustrating is that builtin functions and builtin modules have very different argument processing implementations, despite having exactly the same requirements and exactly the same input types (in the sense of what's passed to the C++ function).
<JordanBrown1>
I'd like to see them unified.
<JordanBrown1>
I suspect that modules got named arguments before functions did, and so functions tend to use infrastructure that's positional-only.
<JordanBrown1>
And of course nothing has very robust type checking; all over the place type mismatches are just ignored.
<JordanBrown1>
(Quick: what does cube(center=5) do?)
<JordanBrown1>
I'm writing a relatively simple function - fn(r|d) that returns the calculated number of sides for a circle of the specified radius or diameter - and I didn't want to write yet another sloppily-checked function.
<JordanBrown1>
So I wanted to take a stab at making a better infrastructure.
<JordanBrown1>
Now, it happens that that one requires something more complex than just the "required()" and "optional()" that I was thinking about so far, but I wanted to start with the simple cases.
<JordanBrown1>
There should be some other simple helper function that says something like "must have exactly one of these arguments".
<JordanBrown1>
So anyhow, underlying the required(name, double) it seems like a reasonable function would be
<JordanBrown1>
again returning a bool, so that it's like the others.
<JordanBrown1>
which would either return true, with a type-checked Value
<JordanBrown1>
or log an appropriate message and return false.
<JordanBrown1>
But if it's really not possible to return a non-primitive as an argument without copying it, that pattern is no good as a general pattern.
<JordanBrown1>
It's pretty clear that returning boost::optional<Value&> works, replacing both the bool and the argument,
<JordanBrown1>
but if you extend that to the per-type functions (give me a double, give me a string, ...) then because there's no unique arguments passed in, only different types coming out, you need different names for each type and that seems kind of untidy.
<JordanBrown1>
Maybe there is some answer involving templates, so that you say "required<double>(...)" or something like that, but... shudder, and that's really not any better than requiredDouble(...).
snaked has joined #openscad
kintel has joined #openscad
<kintel>
JordanBrown1 If such a required() function should log a message, how does it know what to log? Isn't a bunch of context only visible to the caller?
<kintel>
btw. std::optional() can be used now
<JordanBrown1>
If it's a method on Parameters() it knows a bunch of context already.
<JordanBrown1>
Er, drop the () in that.
<JordanBrown1>
I don't think that part of it is hard.
<JordanBrown1>
It knows what the function is (because the function has called Parameters.set_caller())
<JordanBrown1>
it knows the parameter name because that's one of its arguments.
<JordanBrown1>
It knows the desired type.
<JordanBrown1>
For that simple case, what else is there?
<kintel>
And we're talking primitives here right? not Value or anything?
<JordanBrown1>
You mean primitives in a C++ sense, right?
<JordanBrown1>
I wouldn't assume that.
<JordanBrown1>
Not that I've gotten that far, but it seems like one of the variations should be "I require a list".
<JordanBrown1>
Whether that returns a Value (that has been type-checked to be a list)
<JordanBrown1>
or a VectorType or a std::vector, I don't know... whatever is convenient for the caller to then process.
<kintel>
std::optional sounds like a start, but the devil is in the details
<JordanBrown1>
Returning std::optional<something> is an obvious answer, but because the type is not then implied by the arguments it means that you have to have different names for each type.
<kintel>
Are you essentially saying that we're good if we find a way of making std::optional<std::vector<double>> required(const std::string& name); return a vector without making a copy?
<JordanBrown1>
Which isn't the end of the world, of course, but it seemed really ... tidy ... to have the type implied.
<JordanBrown1>
The design I was looking at (that I didn't figure out how to say) wanted an implementation function
<JordanBrown1>
std::optional does not appear to be a drop-in replacement for boost::optional. Or at least I didn't drop it in right.
<JordanBrown1>
It yielded a ton of error messages that I don't understand.
<kintel>
Oh, we already use boost::optional there. Perhaps just continue that then :) ..and we can refactor later
<JordanBrown1>
I was expecting that there might be problems mixing and matching, but it seemed to be complaining at a point that was *not* a mix-and-match point.
<JordanBrown1>
But for the moment I'll just stick with boost::optional.
<kintel>
they're definitely not mixable :)
<JordanBrown1>
:-(
<JordanBrown1>
Can you custom-define a cast?
<JordanBrown1>
actually, I guess that's a copy constructor.
<JordanBrown1>
(?)
<kintel>
You lost me there..
<JordanBrown1>
I would hope that it would be possible to have
<JordanBrown1>
boost::optional<whatever> bo = ...
<JordanBrown1>
std::optional<whatever> so = bo;
<JordanBrown1>
and have it automatically convert the BO to an SO.
<JordanBrown1>
and similarly for passing as an argument, et cetera.
<JordanBrown1>
But that's just having a constructor for SO that takes a BO as an argument, no?
<JordanBrown1>
Or something a lot like that.
<JordanBrown1>
Just like you can automagically convert a double into a Value.
<kintel>
you'd have to own one of the classes I think
<kintel>
Not worth fighting :)
<JordanBrown1>
And not today's problem.
<JordanBrown1>
For the particular case of the function I wanted to define
<JordanBrown1>
fn(r|d)
<JordanBrown1>
there's already a validate_number() function that does the type check
<JordanBrown1>
but that seemed a bit ad hoc; I don't think there's a parallel validate_string(), for instance.
<JordanBrown1>
I was hoping to put together a pattern that would extend to all types.
<JordanBrown1>
For the moment, I wrote Parameters::exactlyOneOf(), that takes a list of names and returns either the one name that's in the argument list, or boost::none if there's none or more than one.
<kintel>
if (value->type() == T) ?
<kintel>
..and make the template parameter template <Value::Type T>
<JordanBrown1>
That, plus the error reporting.
<kintel>
error reporting may need a location
<JordanBrown1>
Which Parameters has.
<kintel>
ah, right
<JordanBrown1>
But like I said, for the simple cases it should be one stop shopping: give me this argument by name, as the type I want, and blow up if it's not there or is the wrong type.
<kintel>
yeah, that's a good start: Make the easy stuff easy and the hard stuff possible
<JordanBrown1>
exactly
<JordanBrown1>
Here's what fn() looks like at the moment: https://bpa.st/EF7A
<JordanBrown1>
it's not awful, but I think it could be better.
<JordanBrown1>
It's not the easy case, because it takes either a radius or a diameter.
<kintel>
If you want to make it even terser, you could do it all in parse()
<kintel>
..but that might be a different exercise :)
<JordanBrown1>
Yes, but then parse() needs a language to describe the parameter types and variations.
<kintel>
a DSL to parse DSL function arguments. hmmm
<JordanBrown1>
yep.
<kintel>
One day we'll write the openscad functions in openscad ;)
<JordanBrown1>
One of the random things that I would like to do is to improve circles, so that they always have vertexes at the poles, and I think a prerequisite for any sort of change like that is to provide a function that tells you how many sides you're going to get.
<JordanBrown1>
so that people don't need to duplicate OpenSCAD's internal calculation to figure out what it's going to do.
<kintel>
How about auto v = parameters.required<Value::NUMBER>(exactlyOneOf({"r", "d"})
<JordanBrown1>
Right now required() doesn't exist, but one problem is that you need to know whether you got a "r" or a "d" so you know whether to divide by two.
<kintel>
Not sure if you're someone who'd appreciate this, but I believe this sort of work is exactly what AI assisted coding would excel at
<JordanBrown1>
Maybe. Not very interested in that.
<kintel>
-> finding the C++ incantations needed to make things fly when given a conversational spec
<JordanBrown1>
ah
<kintel>
Would need a reasonable test case so it could iterate autonomously
<kintel>
btw., don't you also need the function name to issue a proper warning?
<JordanBrown1>
parameters.set_caller("fn");
<JordanBrown1>
though I don't know why that isn't a parameter to Parameters::parse().
<kintel>
..and set_caller is sparingly used
<kintel>
could be another refactor. I just fear a slight expansion in needed function arguments :)
<JordanBrown1>
oooh, yes, very few callers.
<JordanBrown1>
sphere() uses a generic-ish error check for duplicated r/d, and doesn't tell you who the caller is.
<JordanBrown1>
For all of the other cases, it just ignores errors, which makes the error reporting a lot simpler.
<JordanBrown1>
sphere("hello"); is not an error...
<kintel>
also, arguments' loc isn't the same as the loc passed to the function call. I think the former only includes the argument list while the latter includes the whole function call
<kintel>
Might not be an issue though
<JordanBrown1>
ideally, each argument would have its own location.
<JordanBrown1>
and for a type mismatch you would use the appropriate one, but for a missing argument you'd use the function's loc.
<kintel>
true, the argument's location in isolation should be the best to use for this particular case
<JordanBrown1>
But that's a lot finer tuning than seems worthwhile at the moment.
<kintel>
heh
<JordanBrown1>
Also, the more that that kind of thing is centralized, the easier it is to get it all to work consistently.
<JordanBrown1>
If every function does its own type check and its own error message, there will never be consistency.
<JordanBrown1>
But if Parameters::something does the lookup and type check, it can use the loc for *that* argument.
<kintel>
Wait, Parameters is only used for modules, while functions use Arguments? It's been a long time since I looked at this code...
<JordanBrown1>
See above where I lamented that they have the same requirements but use totally different helper functions...
<JordanBrown1>
Parameters.parse() turns an Arguments into a Parameters.
<kintel>
perhaps Arguments should just get the location then.
<kintel>
anyway, it's getting late.
<kintel>
I'm curious where you end up :)
<JordanBrown1>
Yes, I need to stop and do other things.
<JordanBrown1>
Here's what the error handling on that function currently looks like: https://bpa.st/N32Q
<JordanBrown1>
The last two are messages that I didn't play with.
<JordanBrown1>
Actually, so is the invalid-type message. (though, hmm, I don't know where it got the caller name.)
<JordanBrown1>
it gets it from set_caller(), which suggests that something dumb-ish will happen for callers that don't set it.
<JordanBrown1>
Very very few callers.
<JordanBrown1>
But I am sure that somebody will complain if sphere("hello") starts yielding a warning.
<JordanBrown1>
G'nite.
kintel has quit [Quit: My Mac has gone to sleep. ZZZzzz…]
snaked has quit [Quit: Leaving]
sculptor has quit [Quit: Leaving]
sculptor has joined #openscad
TheAssassin has quit [Ping timeout: 272 seconds]
TheAssassin has joined #openscad
Guest53 has joined #openscad
Guest53 has quit [Client Quit]
howiemnt1 has joined #openscad
howiemnt4 has quit [Read error: Connection reset by peer]
mmu_man has joined #openscad
sculptor has quit [Ping timeout: 256 seconds]
JakeSays_ has joined #openscad
JakeSays has quit [Ping timeout: 244 seconds]
shwouchk has joined #openscad
ccox_ has joined #openscad
yogadude` has joined #openscad
peeps[zen] has joined #openscad
shwk has joined #openscad
shwouchk has quit [*.net *.split]
peepsalot has quit [*.net *.split]
yogadude has quit [*.net *.split]
ccox has quit [*.net *.split]
shwk is now known as shwouchk
teepee has joined #openscad
sculptor has joined #openscad
aiyion has quit [Remote host closed the connection]
aiyion has joined #openscad
mmu_man has quit [Ping timeout: 258 seconds]
jbd has joined #openscad
jbd has quit [Ping timeout: 256 seconds]
jbd has joined #openscad
TheAssassin has quit [Ping timeout: 272 seconds]
TheAssassin has joined #openscad
teepee_ has joined #openscad
teepee has quit [Ping timeout: 272 seconds]
teepee_ is now known as teepee
teepee has quit [Remote host closed the connection]
mmu_man has joined #openscad
teepee has joined #openscad
teepee has quit [Remote host closed the connection]
teepee has joined #openscad
mmu_man has quit [Ping timeout: 258 seconds]
teepee has quit [Remote host closed the connection]
teepee has joined #openscad
teepee has quit [Remote host closed the connection]
teepee has joined #openscad
teepee has quit [Remote host closed the connection]
howiemnt1 has quit [Read error: Connection reset by peer]
<JordanBrown1>
I notice that we have both Help/Font List and Window/Font Lists, and they're different. Anybody know what the plan is there?
<teepee>
remove the old one in Help
<JordanBrown1>
Is it ready enough to do that?
<teepee>
I'd say yes
<JordanBrown1>
Not that it directly impacts readiness, but the one in Window tries to have sample text... and doesn't.
<teepee>
meaning what?
<JordanBrown1>
it has a column labelled "Sample text", but that sample text is all in the UI font, not that particular line's font.
<JordanBrown1>
It looks like it's a more functional UI even with that broken, so maybe we should go ahead and strip out the old one, but it's not quite right yet.
<teepee>
maybe a bad side effect from the gui-font setting
<teepee>
not a bug in the window itself
<JordanBrown1>
it's still broken :-)
<JordanBrown1>
Do you mean the style sheet thing we've been looking at?
<teepee>
yes
<JordanBrown1>
yes, that's it
<JordanBrown1>
I commented out that line, and now the window has working sample text.
<JordanBrown1>
Do you happen to know offhand what "term" format output is?
<peeps[zen]>
is that the "CSG products" / "terms"
<JordanBrown1>
It yields "No top-level CSG object" for me.
<JordanBrown1>
Not important; it's just on a testing checklist and I didn't recognize it.
<JordanBrown1>
Since the test is whether or not the file gets created, not whether the output is good, it doesn't really matter.
<kintel>
JordanBrown1 Looks like .term output broke...
shwouchk has quit [Ping timeout: 258 seconds]
<JordanBrown1>
Is that what it's supposed to be, CSG products?
shwouchk has joined #openscad
<kintel>
I believe so; essentially CSG products normalized into sum-of-products form
<kintel>
(used for preview)
<JordanBrown1>
OK. I was just walking your test matrix, and it asked me to export in that form and I didn't know what it meant.
<JordanBrown1>
But since all I'm testing is that it creates a file with the right name, the fact that the contents are wrong or I don't know what they mean is not today's problem.
<JordanBrown1>
While I've got you, you have entries for "utf8-include-a.scad" and "utf-include-b.scad", and similarly for use<>. Any idea what the difference is between the "a" and "b" variants?