Am I missing a point here?
Surely the number 1 requirement of Spin 2 is that it is backwards compatible with Spin 1? No matter what new features Spin 2 may have.

At first blush, it seems great to have 'backward compatible' - however dig even slightly deeper, and you find that is 'backwards compatible' more mirage than reality.
The P2 counters and clocking and PASM are wildly different from P1, so P1 Spin is not 'going to just run', it will need to be scanned and changed, and re-tested.

Since all code requires scan and changes, why not automate that, and widen the customer base at the same time ?

Why alienate those who have taken the trouble to learn Spin, by tweaking the operators or higher level syntax/semantics?

Sure, take that path, and you decide to just clone Spin1 exactly, and add nothing new at all.
Quite easy, nothing to decide at all. Simple Spin2 == Spin1.

That tiny market is somewhat happy, until they discover the P2 Counter and clock and PASM differences mean they cannot just use their P1 Spin sources unchanged .... that 'backward compatible' was actually a mirage.

Jmg. If they are too lazy to be bothered, no amount of spin "me too" will make a difference. However, existing investments are devalued for basically zero net gain, along with a net cost above that which comes with Spin 2 anyway.

Might as well do what was done with P1. Make SPIN, which includes PASM, lean and mean.

It's never going to be standard. Best maximize what it is best at.

..
Might as well do what was done with P1. Make SPIN, which includes PASM, lean and mean.
It's never going to be standard. Best maximize what it is best at.

OK, if that is your target focus (ie a very small set of programmers), and you really are serious about supporting both P2 and P1, then you define Spin2 == Spin1, now really best called Spin1 for P2.

Next, you should add a P1 code generator, so a common parser can be shared, and logically add Conditional Compile, which now allows one source file to build for either P1, or P2, from one EXE.
The Conditional Compile manages those Counter/Timer/PASM areas where P2 silicon is quite different from P1.

As the EXE is revised and improved, P1 and P2 Spin1 can share those improvements, and what you have is Spin1 for P1 and P2.

OK, if that is your target focus (ie a very small set of programmers), and you really are serious about supporting both P2 and P1, then you define Spin2 == Spin1, now really best called Spin1 for P2.

What? Following that logic, PBASIC would never have evolved beyond the BASIC used for the Stamp 1. The P2 will be a more capable chip than the P1, so I see no reason not to enhance Spin2 beyond Spin1's capabilities. Strict compatibility be damned.

OK, if that is your target focus (ie a very small set of programmers), and you really are serious about supporting both P2 and P1, then you define Spin2 == Spin1, now really best called Spin1 for P2.

Next, you should add a P1 code generator, so a common parser can be shared, and logically add Conditional Compile, which now allows one source file to build for either P1, or P2, from one EXE.
The Conditional Compile manages those Counter/Timer/PASM areas where P2 silicon is quite different from P1.

As the EXE is revised and improved, P1 and P2 Spin1 can share those improvements, and what you have is Spin1 for P1 and P2.

That's the approach I used in spin2cpp, back when it was tracking P2. It has the advantage that you can use the new language features on both platforms, and often you can share quite a bit of code between platforms (not the PASM, obviously, but the infrastructure around the PASM). Heck, you can even write Spin code that runs on P1, P2 (an old image), Linux, and Windows using spin2cpp. This approach doesn't mean you have Spin1 for P1 and P2, rather it means that Spin2 is a superset of Spin1.

As far as operator syntax goes my strong preference is to use P1 forms as much as possible. This will make it easier to port Spin tools (like editors, or spin2cpp) to Spin2. Frankly I burned out on trying to keep spin2cpp up to date with P2, but maybe when we have a final chip I'll take it up again. If Spin2 is a superset of Spin1 that task would be a lot easier.

The P2 will be a more capable chip than the P1, so I see no reason not to enhance Spin2 beyond Spin1's capabilities. Strict compatibility be damned.

Of course, any byte-code generator for P2 can already be more capable than P1, as P2 has much more memory and more speed and native Multiply opcodes & better byte-code support.
Those things alone mean the same code will enhance Spin1+ running on P2, much more than if it ran on P1.

If you want to add unique keywords that P1 has no hope of supporting, now you risk falling between two stools, plus that divergence would need compiler directive (much like there are x486 etc compiler switches now).
- yes, result can be faster, but at the cost of portability. Anyone who values portability, will want control over that portability.

That's the approach I used in spin2cpp, back when it was tracking P2. It has the advantage that you can use the new language features on both platforms, and often you can share quite a bit of code between platforms (not the PASM, obviously, but the infrastructure around the PASM). Heck, you can even write Spin code that runs on P1, P2 (an old image), Linux, and Windows using spin2cpp.

This approach doesn't mean you have Spin1 for P1 and P2, rather it means that Spin2 is a superset of Spin1.

This becomes more a semantics issue - I like to call anything that can emit P1 compatible code, 'Spin1', but since this is a better Spin, perhaps it is better called Spin1+, to make it clearer Spin1 is not old-spin.
- Spin1+ is able to create both P1 and P2 byte-code files and byte-code engines.

Sounds pretty much like what you had already.... ?

Instead of an explicit swap operator, how about this?

x, y := y, x

It's much more flexible than just single swaps:

a, b, c := c, a, b ' rotate a, b, c right

The RHS can have whole expressions, too:

PUB gcd(a, b) '' greatest common denominator
repeat while b
a, b := b, a // b
return a

(admittedly, that could be b := (a\b) // b instead)

It's easy to implement on a stack-based architecture like Spin. Evaluate each expression on the RHS of the :=, but leave the result of each expression on the stack without assigning them anywhere yet. Once all the RHS expressions are evaluated and their results are on the stack, assign each one to the list of LHS variables in the correct (reverse of evaluation) order. The compiler just needs to make sure the LHS and the RHS lists have the same number of arguments, to avoid destroying the stack.

For the simple swap case, the compiler could make an exception and emit a special opcode instead of two loads followed by two stores. But the syntax would be consistent with other, more complex uses.

Make a version of spin that is the compatible with the current version of spin with enhancements to take advantage of the increased capability of the new hardware. Make it as close to the current version of spin as is possible. Ken then has something to sell while a really killer language is developed to take advantage of ALL the features in the P2. Most industrial strength stuff will be done in assembly anyway.

The point is, any language can evolve but there must be something to start with.

Instead of an explicit swap operator, how about this?

x, y := y, x

It's much more flexible than just single swaps:

a, b, c := c, a, b ' rotate a, b, c right

The RHS can have whole expressions, too:

PUB gcd(a, b) '' greatest common denominator
repeat while b
a, b := b, a // b
return a

(admittedly, that could be b := (a\b) // b instead)

It's easy to implement on a stack-based architecture like Spin. Evaluate each expression on the RHS of the :=, but leave the result of each expression on the stack without assigning them anywhere yet. Once all the RHS expressions are evaluated and their results are on the stack, assign each one to the list of LHS variables in the correct (reverse of evaluation) order. The compiler just needs to make sure the LHS and the RHS lists have the same number of arguments, to avoid destroying the stack.

For the simple swap case, the compiler could make an exception and emit a special opcode instead of two loads followed by two stores. But the syntax would be consistent with other, more complex uses.

Okay. That's REALLY neat!

I'm trying to get my head around the ramifications of doing something like that. I guess certain routines that return two values will have to have two recipients.

Instead of an explicit swap operator, how about this?

x, y := y, x

It's much more flexible than just single swaps:

a, b, c := c, a, b ' rotate a, b, c right

The RHS can have whole expressions, too:

PUB gcd(a, b) '' greatest common denominator
repeat while b
a, b := b, a // b
return a

(admittedly, that could be b := (a\b) // b instead)

It's easy to implement on a stack-based architecture like Spin. Evaluate each expression on the RHS of the :=, but leave the result of each expression on the stack without assigning them anywhere yet. Once all the RHS expressions are evaluated and their results are on the stack, assign each one to the list of LHS variables in the correct (reverse of evaluation) order. The compiler just needs to make sure the LHS and the RHS lists have the same number of arguments, to avoid destroying the stack.

For the simple swap case, the compiler could make an exception and emit a special opcode instead of two loads followed by two stores. But the syntax would be consistent with other, more complex uses.

Okay. That's REALLY neat!

I'm trying to get my head around the ramifications of doing something like that. I guess certain routines that return two values will have to have two recipients.

Functions actually returning multiple values seems like it would be more complicated than just evaluating multiple single values and assigning them later. In addition to the parameter counting problem, you'd now also have to deal with counting return values.

All of the following assumes the P2 interpreter's stack works similarly to the P1 PNUT stack:

For the return value counting problem, when the caller drops anchor, it could specify how many return values it wants, and then when the return is being processed, it longmove's that many down to the expected place. If the function doesn't return enough values, the rest are uninitialzed, and if it return too many, they are just ignored. This seems like it might be too much overhead for every function call.

For the parameter counting problem itself, the first byte of the function could be the expected parameter count. When a call is performed, dcurr is initialized to @result[byte[pcurr++]], regardless of how many parameters were actually pushed after the drop anchor. Passing too many arguments would preset local variables (which aren't initialized anyway), and not passing enough would leave some parameters uninitialzed. This would allow vararg functions, by just not pushing enough arguments and leaving them unitialized, at the expense of wasting some stack space.

I think this group assignment stuff will be pretty straightforward. It cleans up the functions that return more than one value.

Group assignments:

(,,,) := (x,y) := (a,b) assign 17 set (x,y) to (a,b), 2..16 variables
Math functions
---------------------------------------------------------------------------------------------
(x,y) := ROTXY(x,y,t) Rotate cartesian (x,y) by t and assign resultant (x,y)
(r,t) := XYPOL(x,y) Convert cartesian (x,y) to polar and assign resultant (r,t)
(x,y) := POLXY(r,t) Convert polar (r,t) to cartesian and assign resultant (x,y)

All operators:

Operator Term Usage Assign Usage Type Prior Description
-------------------------------------------------------------------------------------------------------------------------
++ (pre) ++x ++x var prefix 1 Pre-increment
-- (pre) --x --x var prefix 1 Pre-decrement
?? (pre) ??x ??x var prefix 1 XORO32, iterate x and return pseudo-random
++ (post) x++ x++ var postfix 1 Post-increment
-- (post) x-- x-- var postfix 1 Post-decrement
!! (post) x!! x!! var postfix 1 Post-boolean NOT
! (post) x! x! var postfix 1 Post-bitwise NOT
\ (post) x\y x\y var postfix 1 Post-set to y
! !x != x unary 2 Bitwise NOT, 1's complement
- -x -= x unary 2 Negation, 2's complement
ABS ABS x ABS= x unary 2 Absolute value
ENCOD ENCOD x ENCOD= x unary 2 Encode MSB, 31..0
DECOD DECOD x DECOD= x unary 2 Decode, 1 << (x & $1F)
ONES ONES x ONES= x unary 2 Count ones
SQRT SQRT x SQRT= x unary 2 Square root of unsigned x
LOG LOG x LOG= x unary 2 Unsigned to logarithm
EXP EXP x EXP= x unary 2 Logarithm to unsigned
>> x >> y x >>= y binary 3, 16 Shift right, insert 0's
<< x << y x <<= y binary 3, 16 Shift left
SAR x SAR y x SAR= y binary 3, 16 Shift right, insert MSB's
ROR x ROR y x ROR= y binary 3, 16 Rotate right
ROL x ROL y x ROL= y binary 3, 16 Rotate left
REV x REV y x REV= y binary 3, 16 Reverse y LSBs of x and zero-extend
ZEROX x ZEROX y x ZEROX= y binary 3, 16 Zero-extend above bit y
SIGNX x SIGNX y x SIGNX= y binary 3, 16 Sign-extend from bit y
& x & y x &= y binary 4, 16 Bitwise AND
^ x ^ y x ^= y binary 5, 16 Bitwise XOR
| x | y x |= y binary 6, 16 Bitwise OR
* x * y x *= y binary 7, 16 Signed multiply
/ x / y x /= y binary 7, 16 Signed divide, return quotient
// x // y x //= y binary 7, 16 Signed divide, return remainder
SCA x SCA y x SCA= y binary 7, 16 Unsigned scale (x * y) >> 32
SCAS x SCAS y x SCAS= y binary 7, 16 Signed scale (x * y) >> 30
FRAC x FRAC y x FRAC= y binary 7, 16 Unsigned fraction {x, 32'b0} / y
+ x + y x += y binary 8, 16 Add
- x - y x -= y binary 8, 16 Subtract
#> x #> y x #>= y binary 9, 16 Ensure x => y, signed
<# x <# y x <#= y binary 9, 16 Ensure x <= y, signed
< x < y relational 10 Signed less than (returns 0 or -1)
<= x <= y relational 10 Signed less than or equal (returns 0 or -1)
== x == y relational 10 Equal (returns 0 or -1)
<> x <> y relational 10 Not equal (returns 0 or -1)
>= x >= y relational 10 Signed greater than or equal (returns 0 or -1)
> x > y relational 10 Signed greater than (returns 0 or -1)
<=> x <=> y relational 10 Signed comparison (returns -1,0,1 if <,=,>)
!!, NOT !!x !!= x unary 11 Boolean NOT (x == 0, returns 0 or -1)
&&, AND x && y x &&= y binary 12 Boolean AND (x <> 0 AND y <> 0, returns 0 or -1)
^^, XOR x ^^ y x ^^= y binary 13 Boolean XOR (x <> 0 XOR y <> 0, returns 0 or -1)
||, OR x || y x ||= y binary 14 Boolean OR (x <> 0 OR y <> 0, returns 0 or -1)
? : x ? y : z ternary 15 Choose between y and z
:= x := y x := y assign 16 Set x to y
(,,,) := (x,y) := (a,b) assign 17 set (x,y) to (a,b), 2..16 variables
Math functions
---------------------------------------------------------------------------------------------
(x,y) := ROTXY(x,y,t) Rotate cartesian (x,y) by t and assign resultant (x,y)
(r,t) := XYPOL(x,y) Convert cartesian (x,y) to polar and assign resultant (r,t)
(x,y) := POLXY(r,t) Convert polar (r,t) to cartesian and assign resultant (x,y)
Miscellaneous
---------------------------------------------------------------------------------------------
SWAP(x,y) Swap variables x and y (uses '\' bytecode for efficiency)

I like the parentheses. I was going to ask what you were going to do about the following example, but the parentheses solve it nicely. If func is a function that returns an unknown number of results (i.e. a function pointer, but I don't know spin2 function pointer syntax), then:

a, b, c := x, y, (z := func) ' func returns 1 value into z, then (a, b, c) := (x, y, z)
a, b, c := x, y, z := func ' ambiguous or up to operator precedence, could be either of the following
(a, b, c) := (x, y, z := func) ' func returns 1 value into z, then (a, b, c) := (x, y, z)
(a, b, c) := (x, y, z) := func ' func returns 3 values into x, y, z and also into a, b, c

Will you allow chained multiple assignments? The only way I can see to implement it that doesn't involve stack trickery is to just have a big "assign the top n stack elements to the following list of a variables" opcode.

I like the parentheses, too. Although not strictly necessary from a syntactic standpoint, they reinforce the notion that operations within them are completed before the assignment is made; and the code is more readable.

Regarding (a, b) := (p, q) := (x, y), no stack trickery is necessary -- albeit with some lack of efficiency:

push x
push y
pop q
pop p
push p
push q
pop b
pop a

As far as operator syntax goes my strong preference is to use P1 forms as much as possible. This will make it easier to port Spin tools (like editors, or spin2cpp) to Spin2. Frankly I burned out on trying to keep spin2cpp up to date with P2, but maybe when we have a final chip I'll take it up again. If Spin2 is a superset of Spin1 that task would be a lot easier.

Eric

Seconded.

Add only what is needed for the smaller set of new features possible, and remove everything possible, like SPIN keywords for PASM.

Since we are adding inline PASM, those aren't needed.

The product of all that will be sweet.

A P1 user will grok the basics, find out the rest is either a new thing like real object pointers, or is done with a bit of PASM. Rock and roll.

I LIKE what you did for inline ersmith, BTW. Ultra clean, simple, in the spirit of P1 tools.

I would love to, but what you propose is how Phil would also like them to be, ideally, which is different from how Spin1 works.

In Spin1, ~> means SAR and ->, <- mean ROR, ROL.

I really would like -> for SAR and ~>, <~ for ROR, ROL. I wish Spin1 had it that way, in the first place.

Would people flip out if we changed those operators? I would love to do it.

A key question on all this, is will this 'new spin' also be able to emit P1 bytecodes ?

If you do generate both P1 and P2, then there is a natural language tracking, and compatibility, and you have to worry less about old-spin preservation, as P1 users will move to the new-spin.

If the new spin does not / will not emit P1 bytecodes then you do have to maintain, and try to juggle many more conflicting requirements. (ugh)

It also greatly reduces the new-user catchment, which would worry me, but many seem happy targeting just the present Spin user base.

Chip,
Can you please put back ~>, <~, ->, and also change SWAP to be an operator ( i.e. X :=: Y ) and dump the SWAP(X, Y) form?

I would love to, but what you propose is how Phil would also like them to be, ideally, which is different from how Spin1 works.

In Spin1, ~> means SAR and ->, <- mean ROR, ROL.

I really would like -> for SAR and ~>, <~ for ROR, ROL. I wish Spin1 had it that way, in the first place.

Would people flip out if we changed those operators? I would love to do it.

We could change Spin 1 to be that way in OpenSpin quite trivially. It would have to be an option of course. However, if you don't like that, then I still prefer having it like Spin 1 was than having the SAR/ROR/ROL syntax only. This goes back to what I said earlier, I think we should keep all the Spin 1 operators (with the exception of the little used ?) in Spin 2, and than just add the new Spin2 operators and variants.

Also, as jmg says, we could make a new variant of Spin for the P1. So there would be the existing Spin for P1, and then Spin2 for P2 and P1. Where Spin2 for P1 is just the new language features/operators/etc. that can be made to work using existing Spin P1 bytecodes. This would not be something that would take Spin2 for P2 and somehow make it work on P1 (since it can't). It's more like a Spin 1.5.

Could the single ? random operator be allowed to work in contexts where it's obvious from the next token that it's not part of a ternary operator? A warning should be emitted, but it could still be accepted, to make porting easier.

Could the single ? random operator be allowed to work in contexts where it's obvious from the next token that it's not part of a ternary operator? A warning should be emitted, but it could still be accepted, to make porting easier.

Yes. I just need to think through the contexts. I think it should work.

## Comments

15,012The P2 counters and clocking and PASM are wildly different from P1, so P1 Spin is not 'going to just run', it will need to be scanned and changed, and re-tested.

Since all code requires scan and changes, why not automate that,

widen the customer base at the same time ?andSure, take that path, and you decide to just clone Spin1 exactly, and

add nothing new at all.Quite easy, nothing to decide at all. Simple Spin2 == Spin1.

That tiny market is somewhat happy, until they discover the P2 Counter and clock and PASM differences mean they cannot

just usetheir P1 Spin sources unchanged .... that 'backward compatible' was actually a mirage.21,230Outputs:

9

25

No fuss no muss.

That is Javascript.

Pulling that off with a compiled language might be a bit tricky (See varargs in C and templates in C++)

Being able to do that kind of thing is why so many programmers, brought up on strict type checking languages, hate Javascript!

10,246Might as well do what was done with P1. Make SPIN, which includes PASM, lean and mean.

It's never going to be standard. Best maximize what it is best at.

Script investments go the other way, to C.

15,012OK, if that is your target focus (ie a very small set of programmers), and you really are serious about supporting

bothP2 and P1, then you define Spin2 == Spin1, now really best calledSpin1 for P2.Next, you should add a P1 code generator, so a common parser can be shared, and logically add Conditional Compile, which now allows

one source file to build for either P1, or P2, from one EXE.The Conditional Compile manages those Counter/Timer/PASM areas where P2 silicon is quite different from P1.

As the EXE is revised and improved, P1 and P2 Spin1 can share those improvements, and what you have is

Spin1 for P1 and P2.23,514-Phil

14,4885,670That's the approach I used in spin2cpp, back when it was tracking P2. It has the advantage that you can use the new language features on both platforms, and often you can share quite a bit of code between platforms (not the PASM, obviously, but the infrastructure around the PASM). Heck, you can even write Spin code that runs on P1, P2 (an old image), Linux, and Windows using spin2cpp. This approach doesn't mean you have Spin1 for P1 and P2, rather it means that Spin2 is a superset of Spin1.

Eric

5,670Eric

15,012Of course, any byte-code generator for P2 can

alreadybe more capable than P1, as P2 has much more memory and more speed and native Multiply opcodes & better byte-code support.Those things alone mean the

same codewill enhance Spin1+ running on P2, much more than if it ran on P1.If you want to add unique keywords that P1 has no hope of supporting, now you risk falling between two stools, plus that divergence would need compiler directive (much like there are x486 etc compiler switches now).

- yes, result can be faster, but at the cost of portability. Anyone who values portability, will want control over that portability.

15,012Sounds a good approach.

This becomes more a semantics issue - I like to call anything that can emit P1 compatible code, 'Spin1', but since this is a better Spin, perhaps it is better called Spin1+, to make it clearer Spin1 is not old-spin.

- Spin1+ is able to create both P1 and P2 byte-code files and byte-code engines.

Sounds pretty much like what you had already.... ?

1,555It's easy to implement on a stack-based architecture like Spin. Evaluate each expression on the RHS of the :=, but leave the result of each expression on the stack without assigning them anywhere yet. Once all the RHS expressions are evaluated and their results are on the stack, assign each one to the list of LHS variables in the correct (reverse of evaluation) order. The compiler just needs to make sure the LHS and the RHS lists have the same number of arguments, to avoid destroying the stack.

For the simple swap case, the compiler could make an exception and emit a special opcode instead of two loads followed by two stores. But the syntax would be consistent with other, more complex uses.

23,514Now, how about vector arithmetic?

(x, y) := 2 * (a, b)

equivalent to

(x, y) := 2 * a, 2 * b

-Phil

13,910Phil, great idea. I'll put that in.

360The point is, any language can evolve but there must be something to start with.

Sandy

13,910Okay. That's REALLY neat!

I'm trying to get my head around the ramifications of doing something like that. I guess certain routines that return two values will have to have two recipients.

1,555Functions actually returning multiple values seems like it would be more complicated than just evaluating multiple single values and assigning them later. In addition to the parameter counting problem, you'd now also have to deal with counting return values.

All of the following assumes the P2 interpreter's stack works similarly to the P1 PNUT stack:

For the return value counting problem, when the caller drops anchor, it could specify how many return values it wants, and then when the return is being processed, it longmove's that many down to the expected place. If the function doesn't return enough values, the rest are uninitialzed, and if it return too many, they are just ignored. This seems like it might be too much overhead for every function call.

For the parameter counting problem itself, the first byte of the function could be the expected parameter count. When a call is performed, dcurr is initialized to @result[byte[pcurr++]], regardless of how many parameters were actually pushed after the drop anchor. Passing too many arguments would preset local variables (which aren't initialized anyway), and not passing enough would leave some parameters uninitialzed. This would allow vararg functions, by just not pushing enough arguments and leaving them unitialized, at the expense of wasting some stack space.

13,910Group assignments:

All operators:

1,555Will you allow chained multiple assignments? The only way I can see to implement it that doesn't involve stack trickery is to just have a big "assign the top n stack elements to the following list of a variables" opcode.

2,996Can you please put back ~>, <~, ->, and also change SWAP to be an operator ( i.e. X :=: Y ) and dump the SWAP(X, Y) form?

1,555These: could compile to:

23,514Regarding (a, b) := (p, q) := (x, y), no stack trickery is necessary -- albeit with some lack of efficiency:

-Phil

10,246Seconded.

Add only what is needed for the smaller set of new features possible, and remove everything possible, like SPIN keywords for PASM.

Since we are adding inline PASM, those aren't needed.

The product of all that will be sweet.

A P1 user will grok the basics, find out the rest is either a new thing like real object pointers, or is done with a bit of PASM. Rock and roll.

I LIKE what you did for inline ersmith, BTW. Ultra clean, simple, in the spirit of P1 tools.

13,910I would love to, but what you propose is how Phil would also like them to be, ideally, which is different from how Spin1 works.

In Spin1, ~> means SAR and ->, <- mean ROR, ROL.

I really would like -> for SAR and ~>, <~ for ROR, ROL. I wish Spin1 had it that way, in the first place.

Would people flip out if we changed those operators? I would love to do it.

15,012A key question on all this, is

will this 'new spin' also be able to emit P1 bytecodes ?If you do generate

bothP1 and P2, then there is a natural language tracking, and compatibility, and you have to worry less about old-spin preservation, as P1 users will move to the new-spin.If the new spin does not / will not emit P1 bytecodes then you do have to maintain, and try to juggle many more conflicting requirements. (ugh)

It also greatly reduces the new-user catchment, which would worry me, but many seem happy targeting just the present Spin user base.

2,996We could change Spin 1 to be that way in OpenSpin quite trivially. It would have to be an option of course. However, if you don't like that, then I still prefer having it like Spin 1 was than having the SAR/ROR/ROL syntax only. This goes back to what I said earlier, I think we should keep all the Spin 1 operators (with the exception of the little used ?) in Spin 2, and than just add the new Spin2 operators and variants.

Also, as jmg says, we could make a new variant of Spin for the P1. So there would be the existing Spin for P1, and then Spin2 for P2 and P1. Where Spin2 for P1 is just the new language features/operators/etc. that can be made to work using existing Spin P1 bytecodes. This would not be something that would take Spin2 for P2 and somehow make it work on P1 (since it can't). It's more like a Spin 1.5.

1,55513,910Yes. I just need to think through the contexts. I think it should work.

5,67015,012yes, I like that - very clear in intent. - google even finds it is already a 'used practice', on a language without an inbuilt ternary :

Oxygene addresses this gap by simply re-purposing the if statement as an expression:

x := if divisor <> 0 then numerator div divisor else 0;

13,910a =: flag ? b : c

This efficiency scales with compound ternaries.