Skip to content

Conversation

@jordanbrown0
Copy link
Contributor

[ Not ready for merge ]

Implements the remaining "object" features from OEP8, as extracted in OEP8a.

This adds object literals in a JavaScript-like syntax, including object comprehensions.
For a more complete writeup see OEP8a, but briefly:

  • { name: value, ... } - object with constant identifier-safe names
  • { "name": value, ... } - object with constant names not limited to identifiers
  • { (expr): value, ... } - object with names from expressions
  • { for (...) (name): value, ... } - generate several members
  • { if (...) name: value else name: value, ... } - conditionally generate members
  • { let(...) ... } - set variables and evaluate object comprehension
  • { each object } - split up and add each of its members
  • Changes echo() and str() form to use this syntax.

This PR is not ready for merge. I wrote it a couple of years ago as part of PR#4478, and extracted it. I haven't reviewed it yet, but others are welcome to take a look.

@pkriens
Copy link
Contributor

pkriens commented Aug 9, 2025

You really need all the seemingly esoteric options? The all can be done in other ways and they will require a lot of real estate in the cheat sheet? Maybe I misread the idea of OpenSCAD but I understood it was to keep the language as simple as possible.

Also, by using expr for the name you have a lot of error cases to handle or do you want to handle non-string keys?

For simplicity and consistency, I'd follow the object() syntax we now have as close as possible to prevent a lot of confusion and keep the cheat sheet small?

{ name: value, ... } – { expr = value }
{ "name": value, ... } – { expr = value }
{ expr: value, ... } – { expr = value }
{ for (...) (name): value, ... } – { [ for (...) [name, value]] , ... }
{ if (...) name: value else name: value, ... } { [(...) ? [ name, value] else [name,value]], ... }
{ let(...) ... } – let (...) { ... } ?
{ each object } – { object }

If you think it is important to have ':' as separator for JS compatibility then lets introduce a ':' operator that appends? This would match very well with ranges as well as the syntax of object().

a:b -> [a,b]
a:b:c -> [a,b,c]
[a,b]:c -> [a,b,c]

I think matches is the syntax of range already? Just an idea.

@jordanbrown0
Copy link
Contributor Author

You really need all the seemingly esoteric options? The all can be done in other ways and they will require a lot of real estate in the cheat sheet?

The object comprehensions (for, if, let) are all intended to match the existing list comprehensions. Over the course of the discussion (mostly a couple of years ago) one of the biggest questions seemed to be whether we needed object(). object() is syntactically safe, but doesn't line up well with how things are done for lists.

But yes, everything that you can do with object comprehensions you can do with object() and list comprehensions.

Also, by using expr for the name you have a lot of error cases to handle or do you want to handle non-string keys?

Right now I want to prohibit non-string keys, with the intent that they might eventually be allowed. I wouldn't be surprised if there are currently bug-oids where non-strings are converted to strings.

But many of the interesting applications require dynamically constructed keys, both for creating objects and accessing them.

For simplicity and consistency, I'd follow the object() syntax we now have as close as possible to prevent a lot of confusion and keep the cheat sheet small?

Note that object() does not have a syntax. It is just a function, with standard function syntax.

{ name: value, ... } – { expr = value }

This suggests that to create an object in the simplest case you have to put quotes around all of your names: { "a"=1, "b"=2, ...} versus {a: 1, b: 2, ...}. If JavaScript usage is at all an indicator, you really want object creation to be terse in the common cases. (But: JS doesn't have named parameters, and objects are a common JS idiom to implement an equivalent. OpenSCAD has named parameters.)

{ for (...) (name): value, ... } – { [ for (...) [name, value]] , ... }

Plausible.

{ if (...) name: value else name: value, ... } { [(...) ? [ name, value] else [name,value]], ... }

In my quick summary above, I used a full if...else, but you are also allowed to conditionally include a value by just saying if (...) name: value. That doesn't mesh well with using a ternary ?:.

{ let(...) ... } – let (...) { ... } ?

Paralleling list comprehension. Putting the "let" outside the object literal widens its scope, which may be undesirable in complex constructions. You can also use the let() operator inside an expression, but that narrows the scope undesirably; it can't wrap around a for...if comprehension.

{ each object } – { object }

Again, paralleling list comprehension. In list comprehension, you need "each" because otherwise there is syntactically no way to distinguish "pull apart this list and add its members individually" from "add this list as an element". Object comprehension doesn't have that particular requirement, because an expression doesn't syntactically match "name: value".

Note that for both list comprehension and object comprehension, "each" is equivalent to a "for" loop that iterates across the source list/object.

If you think it is important to have ':' as separator for JS compatibility

Being like JS and Python - this has similarities to both JS object syntax and Python dictionary syntax - is certainly good, but the major reason that it uses : instead of = is that it isn't top-level OpenSCAD syntax.

  • (I started out with an object syntax that was top-level OpenSCAD syntax, with the variables in that scope being the members of the object, and any geometric output available as a magic part of the object. After flipping that coin back and forth a few times, I decided that I liked the separate syntaxes and separate data types for object literals and geometry literals. I believe I left a writeup of that scheme in OEP8 as a historical note.)

then lets introduce a ':' operator that appends? This would match very well with ranges as well as the syntax of object().

I don't think that can work with ?:.

I think matches is the syntax of range already? Just an idea.

Ranges have brackets around them. Actually, your suggestion there would conflict with ranges: is [a:b] a range or is it a vector whose sole element is the concatenation of a and b?

@jordanbrown0
Copy link
Contributor Author

A slight simplification would be to eliminate the { "name" : value } syntax, the syntax that allows for constant names that are not identifier-safe. It's copied from JavaScript, but is equivalent to { ( "name" ) : value }. It's a rare enough case that the additional verbosity isn't bad.

However, I think this syntax is the same as JSON, and JSON always puts quotes around its names. In fact, this syntax is close enough to JSON that we should probably ensure that it is JSON-compatible, that a block of JSON data would be directly usable as OpenSCAD input, and that the result of an echo() or str() in OpenSCAD is valid JSON data.

@pkriens
Copy link
Contributor

pkriens commented Aug 9, 2025

Note that object() does not have a syntax. It is just a function, with standard function syntax.
a) that is definitely syntax
b) with the list and object parameters we definitely add a complex syntax layer on a normal function call. That syntax is non-obvious.

I don't think that can work with ?:.
Matter of priorities ... A ':' would clearly have a higher priority than the ':' in ?:.

Ranges have brackets around them. Actually, your suggestion there would conflict with ranges: is [a:b] a range or is it a vector whose sole element is the concatenation of a and b?

Devil is in the details :-)

Personally I am always easy to convince for any additional functionality but I also see that the drive to keep the cheat sheet small and not create another Perl with 10 ways to do the same thing has a lot of merit?

One issue, will you be able to use the results of a prior field like in let? Eg { a=42, b= 2*a }

@jordanbrown0
Copy link
Contributor Author

I would say that we add complex argument semantics on top of object, without adding any syntax. But I agree that's a quibble.

I don't think that can work with ?:.

Matter of priorities ... A ':' would clearly have a higher priority than the ':' in ?:.

Is a ? b : c : d equivalent to a ? (b:c) : d or a ? b : (c:d)?

I'm not sure without asking Bison's opinion.

But I'm pretty sure that the range thing is a conflict.

One issue, will you be able to use the results of a prior field like in let? Eg { a=42, b= 2*a }

Not at present. They are not variables. That's consistent with JS and Python.

(Just as when constructing a list you cannot refer to previous elements... though with a list there's no obvious way to even try to refer to them.)

@pkriens
Copy link
Contributor

pkriens commented Aug 10, 2025

Is a ? b : c : d equivalent to a ? (b:c) : d or a ? b : (c:d)?
I'm not sure without asking Bison's opinion.

Neither am I but I am used to parsers where you can set the priority. I asked ChaGPT and she says yes ... not that I always trust her. However, I am not fighting for this feature, seems nice but not a big deal.

Not at present. They are not variables. That's consistent with JS and Python.

But not with OpenSCAD's let(...) ... However, as long as my functions in the object can see all the fields I am happy.

@jordanbrown0
Copy link
Contributor Author

jordanbrown0 commented Aug 10, 2025

To clarify a little: it's consistent with Python dictionary syntax. Python objects don't have a distinct syntax; they are populated through normal statement execution and there you can refer to previous assignments. That's not really an option for us, since objects (like all other OpenSCAD values) are immutable and so you can't add new entries to an existing object.

@pkriens

This comment was marked as off-topic.

@jordanbrown0

This comment was marked as off-topic.

@coryrc
Copy link
Contributor

coryrc commented Nov 9, 2025

(Since you still have [ Not ready for merge ] in the OP, please mark this as a Draft PR or remove that note; given the last two (off-topic) comments, it seems the former is more appropriate).

@jordanbrown0 jordanbrown0 marked this pull request as draft November 9, 2025 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants