Parameterized OBJ instantiations
cgracey
Posts: 14,206
Right now, OBJ's get set up like this:
OBJ name[count] : "filename"
Would it be nice to be able to pass compile-time parameters which could override CON symbols in the child object, like this?
OBJ name[count] : "filename" x=1, y=2, z=3
This could allow parameterization from the top object all the way down.
Parameterization would be handy for setting things like VAR buffer sizes in child objects.
Comments
Would the override also be able to include addpins derived values? That would be handy I think, to save a bunch of pin functions.
CON myBasePin 0 addpins 7
OBJ name[count] : "filename" BASEPIN=myBasePin
filename.spin2
CON BASEPIN = 16 addpins 7
Do multiple copies of the same object share their code? It seems like if they had different CON parameters that they couldn't do that.
Here is situation that it would help. (from my ntsc decoder) But I won't request this since I have a solution already.
The ntsc input program always writes longs from the streamer. That seemed to gave the best performance, and later testing on the forum showed performance loss when writing shorts or bytes. So, selecting which ADC pin requires selecting which byte of the long to operate on. Maybe I could have the streamer write at an offset, but that seems like a messy solution.
OBJ objectname : "filename" basepin = 16 addpins 7
...That would override the basepin assignment in the child object.
The objects would become unique if their code became different due to the CON overrides. Then there would be two separate instances in memory.
Good stuff!
This almost seems like an alternative to #ifdef types of compile time options. Is that what you're thinking?
IMO would be even better (at some point) to support ifdefs too, but the CON override has a really useful place, and a pretty clean structure too. I can think of many good uses.
This may be an ugly couple of things to bring up, but At this moment, I am writing P1 code that would benefit from this... what about:
OBJ name[count] : "filename" [0] x=1, y=2, z=3 [1] x=5, y=2, z=7 [and so on]
Well, if anything, this should interact with a possible implementation of #ifdef. Switching sections of code is more useful than just changing CON values. Those can often be runtime variables.
I would find that useful with serial and other buffered objects. ATM, I am updating my serial objects so that I pass the RX and TX buffer sizes along with pointers to RX and TX buffers that live in the parent. The reason, of course, is to not be stuck with the fixed size of buffers in the class file. It would be nice to do this...
...and have the instance buffers (byte arrays) sized from that. As someone else pointed out, I could use this on the P1 right now.
Does this mean if I have two serial objects with different buffer sizes I will have RAM and code space for each? One of the problems with Parallax Spin compilers is using too much space; will this get worse if I use the same class twice with different constant definitions?
OBJ name0 : "filename" x=1, y=2, z=3
OBJ name1 : "filename" x=4, y=5, z=6
OBJ name2 : "filename" x=7, y=8, z=9
Then you could do:
name0[n].go()
IFDEF would be something additional.
There is a niggle with #define functionality. It traditionally exists outside of things like CON sections so can't use those very constants. But #define functionality is very handy for configuring what code sections are compiled at all.
On the other hand, I've been quite surprised how much can be done using CON symbols and the
? :
operator pair in compile time data building.That makes so much sense.. I want it!
You'd want to make sure you are not restricted by some arbitrary line character limit if there are large numbers of parameters needing customization for that feature (or for child objects that have lots of long identifier names etc). Or you could include a technique for line continuation in that case.
It'd be nice to be able to pass down CON defined values, then you could compute your parameters if this is required, rather than setting them all explicitly on the OBJ lines.
I think it would be rather useful to be able to access labels in DAT section like public methods.
Alike
subobject:
DAT
mySetting long 123
mainobject:
OBJ
obj sub : "filename"
...
mainvar:=sub.mySetting
or
sub.mySetting:=456
Mike
(from the signature of @"Phil Pilgrim (PhiPi)" )
(from the "Intuition" manual of the Amiga)
So please do not make the Spin2 language more complicated than neccessary. What could be done with CON-parametrized OBJs what couldn't by passing parameters to the Start() method?
Memory buffer dimensioning was the first example of one use Chip had above. Without dynamic memory in the P2 object layout we need static memory allocation unfortunately which can be limiting and requires specific attention. I can see other uses to customize driver building (not spawning), although IMO the #ifdef feature would help there too. If they could be coupled together that could be even handier...with the child object's ifdef values/expressions being definable/overridable by the parent object for example.
Edit: I think to keep the use of child objects simple it would be good to ensure you don't have to pass anything if you don't want to from the parent and the child would then use its own (default) value of the constant. i.e. make the feature totally optional for ALL child objects, including those that will be able to make good use of it.
What if instead of a text preprocessor, we get statements for conditional assembly in DAT sections (and dead branch elimination in PUB/PRI). That'd make everything come together.
i.e. if/else in DAT sections (would need the end token because no indents)
We might be able to use a similar approach to what GNU make uses with our assignments to our constants in child objects... GNU make uses ?= as the operator to assign if the variable is not already assigned. This could let a child object set a default for a constant and you would explictly know which constant identifiers can be overridden by the parent.
Agree 100%.
In this case, If it is not needed, it adds no complexity. You can do what you want without using this, but it can simplify your code:
I wish I could do this, where I really need the Propeller to just act like multiple chips with no overhead logic:
OBJ
Nest : "Nestlogic" Startpin = 0, Newcog = 1
Nest1: "Nestlogic" Startpin = 20, Newcog = 1
Nest2: "Nestlogic" Startpin = 4, Newcog = 1
Nest3: "Nestlogic" Startpin = 16, Newcog = 0
'Each object statement says it all. In 3 years, I will still understand this at a glance. I won't need to keep the constants straight while passing them in because there they are..
Then:
PUB Start |idx
repeat idx from 0 to 3
NestLogic[idx].go
Yes, I can (And have) do(ne) this without this feature, passing the data in via the go(s,c) and it will still be simple, but this would make the top object so clear, so short..
What if I needed to call the Nest[] from multiple places instead of just starting it? this encapsulation would cut down on on what might be lengthy parameterization.
The only reason I can see for doing something like this is to override object constants (e.g. buffer lengths) during pre-compilation. Everything else can be handled in a start method. Of course this entails multiple copies of the same object that have different constant settings.
-Phil
Yes, it has the sad effect of creating near-identical copies of the same object. How this has been handled in the past was to have the parent declare any large buffer of unknown size and pass a pointer and size info down to the child object. This was a little messier, but resulted in smaller code in the case of there being more than one instance of a child object.
Where this would definitely save memory would be in cases of a single instances of a child object where the I/O pins could be hard-coded. This would get around having to make variables containing pin numbers for specific pins.
Adding this parameterization would be useful in some cases, but would encourage writing objects which are not totally flexible in most cases.
I've never considered that to be a problem. That's what start methods are for.
-Phil
My case for preprocessing/conditional assembly is stuff like this snippet (from NeoYume's memory arbiter cog).
Note that all the constants (such as MA_PCM_ASHIFT) are either dependent on memory type, but that could be (horribly!) faked with ternary operators (could we have constant expression lookupz?).
Setting something like this up entirely through a runtime
start(...)
call is of course possible, just really, really obnoxious. Especially since anything you'd want to reference needs to have a global label, so byebye local ones. (I think flexspin actually can do something likema_do_adpcm.irqshield
to refer to a local label? not sure).This code doesn't even deal with memory writes, which are far more complex...
IMO, programs laden with #ifdefs and others of that ilk are virtually unreadable. The reason is that you constantly have to scroll to the top of a program to see which definitions are in force before you can tell which program sections will be compiled. And the omitted sections are totally distracting from the flow of comprehension.
But there's nothing wrong with having a preprocessor that's a separate program and that produces discrete, preprocessed source-code files, unburdened by #ifdefs, that a human can look at, understand, and that can be fed to a compiler.
I just don't want a compiler that also does preprocessor work, since it hides any results of the preprocessing stage. IOW, I want to see what's actually being compiled.
-Phil
I do understand that point whenever #ifdef is really overdone and nested in large projects as I've suffered wading through that myself however the main problem with going with the separate tool approach is that it makes software requiring any conditional compilation much harder to distribute in source form as it requires additional tools installed in order to even build the code which beginners won't necessarily have present or even understand etc. How does CPP work in Windows for example? Do you need to install MinGW etc? Then the challenge becomes supporting installation of different tools on different platform and educating the users on running Makefiles or build scripts etc and I expect it rapidly becomes a turn off for them. Much handier to have at least some aspects of conditional compilation features built into the widely used toolchains like PropTool and FlexSpin to avoid that overhead.
I do still feel we sort of need some type of built in capability for conditional compilation to reduce code duplication but it probably doesn't have to be the entire preprocessor shebang. Even using existing consts that can be used with some new type of conditional compilation syntax in SPIN could potentially suffice. But I guess that is separate to the original discussion Chip started with OBJ enhancement proposal.
In my example, I could have done what I'm doing with 4 small PICs. The Propeller is just so much easier to load and use (This is on a FLIP BTW). The code size is not a concern in this case. If this were a sophisticated job, where every long counts, it wouldn't make sense to use this, but in this stupid simple application, the encapsulation would be very welcome.
If it makes sense to use this feature (If added) then use it. If it doesn't make sense, don't use it,
*********************I see no penalty to this feature being available. ******************************
(Or preprocessor/conditional assembly for that matter, If it makes sense, use it, but no one will put a gun to your head and insist that you do..)
^ This.
The readability issue can be solved at the editor level. See VSC's C plugin:
(ignore the fact that it is too stupid to notice that WIN32 is defined by the compiler. I'd need to set that somewhere in a config file.)
Kind of funny to read that @cgracey is confused by the readability of ifdef and company.
Skip pattern are way more complicated to follow.
And a seperate precompiler would not be such a mess if the same one could be used and installed by Proptool and Flexspin.
Mike
I like the idea of being able to override base pin settings from top level
I have yet to use a skip pattern. I guess it's because I've not run out of cogRAM in my experimenting so not needed to compact any code.