Shop OBEX P1 Docs P2 Docs Learn Events
Spin2 Operator Syntax - Page 17 — Parallax Forums

Spin2 Operator Syntax

11213141517

Comments

  • jmgjmg Posts: 15,140
    edited 2018-01-17 23:57
    Heater. wrote: »
    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 ?

    Heater. wrote: »
    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.

  • Heater.Heater. Posts: 21,230
    Chip,
    Can you picture getting rid of parameter counting, somehow?
    Sure.
    function doSomething(x, y, z) {
        return x * x
    }
    
    let a = doSomething(3, 2)
    console.log(a)
    
    let b = doSomething(5, 4, 3, 2, 1)
    console.log(b)
    
    Outputs:
    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!

  • potatoheadpotatohead Posts: 10,253
    edited 2018-01-18 00:38
    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.

    Script investments go the other way, to C.


  • jmgjmg Posts: 15,140
    potatohead wrote: »
    ..
    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.


  • jmg wrote:
    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.

    -Phil
  • This thread started out by talking about operator syntax. There is nothing P2-specific about that. These are all just gratuitous changes.
  • ersmithersmith Posts: 5,900
    edited 2018-01-18 02:34
    jmg wrote: »

    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.

    Eric
  • 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
  • jmgjmg Posts: 15,140
    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.

  • jmgjmg Posts: 15,140
    ersmith wrote: »

    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.

    Sounds a good approach.
    ersmith wrote: »
    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.... ?
  • ElectrodudeElectrodude Posts: 1,614
    edited 2018-01-18 04:50
    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.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2018-01-18 05:11
    I like that better. Vector assignments can solve a lot of issues, it seems.

    Now, how about vector arithmetic?

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

    equivalent to

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

    -Phil
  • cgraceycgracey Posts: 14,133
    I like x :=: y for SWAP, with the scalar expression returning the new value of x.

    -Phil

    Phil, great idea. I'll put that in.
  • 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.

    Sandy
  • cgraceycgracey Posts: 14,133
    edited 2018-01-18 05:17
    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.
  • cgracey wrote: »
    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.
  • cgraceycgracey Posts: 14,133
    edited 2018-01-18 13:09
    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.
    (a, b) := (p, q) := (x, y)
    
  • 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?
  • Instead of any explicit swap operator, why don't you make it implicit from a group assignment?

    These:
    (a, b) := (b, a)
    (a, b, c) := (b, c, a)
    (a, b) := (b, a // b)
    
    could compile to:
    a := b\a
    a := b\(c\a)
    b := (a/b) // b
    
  • 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
    

    -Phil
  • ersmith wrote: »
    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.



  • cgraceycgracey Posts: 14,133
    Roy Eltham wrote: »
    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.
  • jmgjmg Posts: 15,140
    cgracey wrote: »
    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.

  • cgracey wrote: »
    Roy Eltham wrote: »
    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.
  • ElectrodudeElectrodude Posts: 1,614
    edited 2018-01-19 00:35
    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.
  • cgraceycgracey Posts: 14,133
    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.
  • We could also use:
      x := if (i<0) then -i else i
    
    for ternary operations. Personally I find it easier to read, although it isn't as terse, and it wouldn't conflict with the existing use of "?".


  • jmgjmg Posts: 15,140
    ersmith wrote: »
    We could also use:
      x := if (i<0) then -i else i
    
    for ternary operations. Personally I find it easier to read, although it isn't as terse, and it wouldn't conflict with the existing use of "?".

    yes, 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;


  • cgraceycgracey Posts: 14,133
    edited 2018-01-19 19:58
    The ternary ? is much more efficient:

    a =: flag ? b : c

    This efficiency scales with compound ternaries.
Sign In or Register to comment.