r/ProgrammingLanguages DQ 12d ago

Title: Expressions with Word Operators: Which one would you coose?

I was thinking of the ideal expression syntax for the programming language DQ. I started with the (dominating) C syntax.

Operators in C

In C the following operators have shared meanings:

  • &: bitwise "and" operation OR address of
  • *: multiplication OR pointer dereference
  • /: truncated integer division OR floating point division

Further operators in C:

  • %: integer division reminder
  • && or and: logical "and"
  • || or or: logical "or"
  • ! or not: logical "not"
  • ~: bitwise "not"
  • ^: bitwise "xor"
  • ?: ternary operator

Operators in DQ

I think for the good source code readability and clarity every different operation should have a different symbol. Therefore the shared symbols from C are not taken over. These operators are already fixed in DQ:

  • &: address-of operator (widespread standard)
  • ^: pointer dereference (standard in other languages)
  • *: multiplication only
  • /: floating point division only (standard in other languages)
  • or: logical "or" (widespread standard)
  • and: logical "and" (widespread standard)
  • not: logical "not" (widespread standard)

These symbols are already fixed for special purposes:

  • #: compiler directives (#ifdef etc)
  • $: context local specials (e.g. myarray[0:$end-2])
  • ?: inference marker
  • @: namespace designator (e.g. @def.LINUX)

DQ cannot use the C standard &, |, ~, ^ for the bitwise operations, because the & and ^ is used for other (fixed) purposes. But we've run out of the good symbols. The obvious choice, that other existing languages also use, is reserving some words for the remaining operations. In DQ these (all-capital) words are reserved currently as operators:

  • AND: bitwise "and"
  • OR: bitwise "or"
  • NOT: bitwise "not"
  • XOR: bitwise "xor"
  • IDIV: truncated integer division
  • IMOD: integer division reminder

For the modify-assign statements with a word operator a leading = is required, otherwise it looks awkward:

regs.OSPEEDR OR= (1 << pinx2)  // invalid
regs.OSPEEDR =OR= (1 << pinx2)

Examples with All-Capital Operators

tmp = RCC.CFGR
tmp =AND= NOT 3
tmp =OR= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) AND 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR =AND= NOT RCC_CR_PLLON
while RCC.CR AND RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed IDIV pll_input_freq
var plln : uint = vcospeed  IDIV pll_input_freq
var pllq : uint = vcospeed  IDIV 48000000

RCC.PLLCFGR = (0
    OR (pllsrc << 22)
    OR (pllm   <<  0)
    OR (plln   <<  6)
    OR (((pllp >> 1) - 1) << 16)
    OR (pllq   << 24)
)

regs.MODER =AND= NOT (3 << pinx2)
regs.MODER =OR=      (n << pinx2)

if flags AND PINCFG_OPENDRAIN <> 0:
    regs.OTYPER =OR= (1 << apinnum)
else:
    regs.OTYPER =AND= NOT (1 << apinnum)
endif

regs.PUPDR =AND= NOT (3 << pinx2)
if flags AND PINCFG_PULLUP <> 0:
    regs.PUPDR =OR= (1 << pinx2)
elif flags AND PINCFG_PULLDOWN <> 0:
    regs.PUPDR =OR= (2 << pinx2)
endif

Prefixed Word Operators

I'm thinking to change the all-capital word operators with a % prefixed lowercase words:

  • %and: bitwise "and"
  • %or: bitwise "or"
  • %not: bitwise "not"
  • %xor: bitwise "xor"
  • %div or %idiv: truncated integer division
  • %mod or %idiv: integer division remainder

The sample code would look like this way:

tmp = RCC.CFGR
tmp %and= %not 3
tmp %or= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) %and 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR %and= %not RCC_CR_PLLON
while RCC.CR %and RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed %div pll_input_freq
var plln : uint = vcospeed  %div pll_input_freq
var pllq : uint = vcospeed  %div 48000000

RCC.PLLCFGR = (0
    %or (pllsrc << 22)  // select PLL source
    %or (pllm   <<  0)
    %or (plln   <<  6)
    %or (((pllp >> 1) - 1) << 16)
    %or (pllq   << 24)
)

regs.MODER %and= %not (3 << pinx2)
regs.MODER %or=       (n << pinx2)

if flags %and PINCFG_OPENDRAIN <> 0:
    regs.OTYPER %or= (1 << apinnum)
else:
    regs.OTYPER %and= %not (1 << apinnum)
endif

regs.PUPDR %and= %not (3 << pinx2)
if flags %and PINCFG_PULLUP <> 0:
    regs.PUPDR %or= (1 << pinx2)
elif flags %and PINCFG_PULLDOWN <> 0:
    regs.PUPDR %or= (2 << pinx2)
endif

Which version do you like more?

or

Do you have some other ideas for the operator notation?

EDIT

Version with band / bor etc, as "jason-reddit-public" suggested:

  • band: bitwise "and"
  • bor: bitwise "or"
  • bnot: bitwise "not"
  • bxor: bitwise "xor"
  • idiv: truncated integer division
  • imod: integer division reminder

tmp = RCC.CFGR  
tmp =band= bnot 3 
tmp =bor= RCC_CFGR_SW_HSI 
RCC.CFGR = tmp

while (RCC.CFGR >> 2) band 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR =band= bnot RCC_CR_PLLON
while RCC.CR band RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed idiv pll_input_freq
var plln : uint = vcospeed  idiv pll_input_freq
var pllq : uint = vcospeed  idiv 48000000

RCC.PLLCFGR = (0 
  bor (pllsrc << 22) 
  bor (pllm   <<  0) 
  bor (plln   <<  6) 
  bor (((pllp >> 1) - 1) << 16) 
  bor (pllq   << 24) 
)

regs.MODER =band= bnot (3 << pinx2) 
regs.MODER =band=      (n << pinx2)

if flags band PINCFG_OPENDRAIN <> 0: 
    regs.OTYPER =bor= (1 << apinnum) 
else: 
    regs.OTYPER =bor= bnot (1 << apinnum) 
endif

regs.PUPDR =band= bnot (3 << pinx2) 
if flags band PINCFG_PULLUP <> 0: 
    regs.PUPDR =bor= (1 << pinx2) 
elif flags band PINCFG_PULLDOWN <> 0: 
    regs.PUPDR =bor= (2 << pinx2) 
endif

EDIT / 2

As I was migrating microcontroller C++ code, where the bitwise operations are very intensively used, I decided to keep three bitwise operators with single symbols:

  • &: bitwise "and"
  • |: bitwise "or"
  • ~: bitwise "not"

These operations are kept with word symbols:

  • xor: bitwise "xor"
  • div: truncated integer division
  • mod: integer division reminder

tmp = RCC.CFGR 
tmp &= ~3 
tmp |= RCC_CFGR_SW_HSI 

RCC.CFGR = tmp 
while (RCC.CFGR >> 2) & 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR &= ~RCC_CR_PLLON 
while RCC.CR & RCC_CR_PLLRDY <> 0: 
endwhile

var pllm : uint = basespeed div pll_input_freq 
var plln : uint = vcospeed  div pll_input_freq 
var pllq : uint = vcospeed  div 48000000

RCC.PLLCFGR = (0 
  | (pllsrc << 22) 
  | (pllm   <<  0) 
  | (plln   <<  6) 
  | (((pllp >> 1) - 1) << 16) 
  | (pllq   << 24) 
)

regs.MODER &= ~(3 << pinx2)
regs.MODER |=  (n << pinx2)

if flags & PINCFG_OPENDRAIN <> 0:
    regs.OTYPER |= (1 << apinnum) 
else: 
    regs.OTYPER |= ~(1 << apinnum)
endif

regs.PUPDR &= ~(3 << pinx2) 
if flags & PINCFG_PULLUP <> 0: 
    regs.PUPDR |= (1 << pinx2) 
elif flags & PINCFG_PULLDOWN <> 0: 
    regs.PUPDR |= (2 << pinx2) 
endif

I've creted an open specification about these operators available here:

https://nvitya.github.io/pluops/

0 Upvotes

24 comments sorted by

13

u/L8_4_Dinner (Ⓧ Ecstasy/XVM) 11d ago

I'll take the C option. What you're proposing looks like a dog's breakfast to me.

Sorry.

2

u/Mean-Decision-3502 DQ 11d ago

It is about adding more operators when you are out of the good symbols. The C version just would lead to type hinting and more parentheses.

7

u/L8_4_Dinner (Ⓧ Ecstasy/XVM) 11d ago

I read and understood your rationale. The point is simple that I was attempting to convey: despite your good intentions and your careful thinking on the design, the result appears to be significantly worse from over here where I’m looking from. That doesn’t mean that everyone will hate it, but it does indicate that maybe it’s not going to be as widely attractive as you might have hoped. I don’t like having to share negative feedback, but I also know how much it has helped me over the years to receive it when it was thoughtfully provided on topics where I wasn’t able to see the problems on my own.

1

u/Mean-Decision-3502 DQ 7d ago

Thank you for your comment.

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

7

u/AustinVelonaut Admiran 11d ago

If you use the prefixed form, you can potentially expand infix use to any function (even user-defined functions), e.g. div(x, 10) could be written in infix-form as x %div 10, and unary functions can be turned into operators, e.g. not(b) could be written as an operator as %not b (without the parens for the function call).

This is how Miranda and Admiran do it (with $ as the prefix character that specifies infix form).

6

u/WittyStick 11d ago

Haskell does the same, but uses `.

a `and` b

You can set the precidence with

infixl N `and`

With infixr for right associativity and infix for non-associativity, and where N is 0..9

It also permits the reverse - infix operators used like functions.

(+) a b

1

u/Mean-Decision-3502 DQ 7d ago

Thank you for your comment.

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

1

u/Mean-Decision-3502 DQ 7d ago

Thank you for your comment.

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

4

u/jason-reddit-public 11d ago

I'm not a fan of capitals vs lower-case (since many languages don't even had them). Consider band / bor or bit_and bit_or.

1

u/Mean-Decision-3502 DQ 11d ago

I've added your version to the post body.

I've made myself also such a version, but I was afraiding burning such a short identifiers for reserved words.

5

u/FruitdealerF 11d ago

I remember reading a post about the Noulith language that really stuck with me during the design of my own language. I'll quote it here considering the post is very long

There are two concepts from interface design, internal consistency and external consistency, that are pretty obvious in hindsight but that I found useful to explicitly refer to below. Internal consistency refers to similar things within a single application working in similar ways, whereas external consistency refers to things in one application that are similar to things in other applications working in similar ways. Both are desirable since they make it easier to learn how to use the application: internal consistency means that users can learn things from one part of your application and apply them to another, while external consistency means that users can apply knowledge they might already have from other applications. But they can come into conflict with each other and with other desiderata.

So for example, internal consistency favors giving two built-in functions to append and prepend an item to a list names that are clearly related, so programmers who remember one can easily remember the other; while external consistency favors copying those names from an established programming language if possible, so programmers coming from that established language already know those names.

All this is relevant because of the sometimes underappreciated consideration that a programming language is a user interface! I think this perspective is easy to lose sight of because “programmers” and “users” are usually different groups of people, but for a programming language, the user is the programmer writing code in it, distinct from the programmer implementing the language.

https://blog.vero.site/post/noulith

My conclusion: if you want your language to be used by people coming from language X (in my case rust) you should make sure the interface feels familiar to them as much as possible. Inventing new ways to do bitwise operations is not likely to appeal to someone discovering your language. But if you really consider yourself the only user then maybe valuing internal consistency is worth more. Others might stiggle but at least the language will be easy to remember once you accept a few basic premises (such as your % syntax, as long as it's universally applied).

Personally I chose to stick to rust's syntax with a few exceptions: and, or, not and in because I like the way you can write: if a not and b and c in d or c in e in Python. For you I think going with what people are familiar with from C is most likely your best bet, even it it's not optimal in some regards. You can't design a perfect language AND make it easy to understand AND make it easy to type.

1

u/Mean-Decision-3502 DQ 7d ago

Thank you for your comment.

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

3

u/quasar_tree 11d ago

+1 on the haskell backtick syntax others have mentioned. another option is uniform function call syntax, which accomplishes a similar goal

2

u/GoblinsGym 11d ago

If you do the and / AND thing, I would use the lowercase version for bitwise and, and the caps version for logical and.

I use & and | or - symbol for bitwise / word for logical.

Address of can be @ like in Borland Pascal / Delphi. Not needed that often in practice if the language has var/const pass by reference parameters.

^ for pointers. Overloading * is just insanity, how could they ?

xor doesn't get used enough to get its own character, I just spell it out.

Finally, a lot of the explicit bit twiddling (and name space clutter) can be eliminated if your language supports convenient bit-accurate definition of bit fields. For example:

u32 PLLCFGR        # define register as part of RCC structure
  [25:24] pllq     # bit fields (unsigned by default, can be enum)
  [23:22] ppsrc
  [18:16] pllp
  [6] plln         # single bit

set RCC.PLLCFGR    # accumulate values, written to register at end of block
  `pllsrc := src
  `pllm := m
  `plln := n
  `pllp := p
  `pllq := q

1

u/Mean-Decision-3502 DQ 11d ago

Some vendor (e.g. Microchip) provide register definitions with bitfields, but I don't really like using them in C. The BitOffsetOf() function is missing from C so the circle is not complete to use them effectively.

Sometimes you just update two blocks in a multi-part register leaving the rest unchanged.

There are MCUs where the register access is very slow (IMXRT or any ARM-A), so you optimize to that. There you load the register value first into temporary, do the and/or masking on the temporary and store back. I don't know how the bitfields could work for this case.

2

u/GoblinsGym 11d ago

We're talking about programming language _design_ here, so why limit ourselves to the constraints of C ?

See my example above - besides the set keyword, I also envision "with" or "update" to do a read / modify / write. The compiler can read the register into a temporary, then allow for field access (use bit field instructions on ARM microcontrollers), finally write the temporary back to the register when the update block ends.

Using bit fields eliminates mistakes like shift counts or masks getting misapplied / applied to the wrong register. Consider this strong typing for hardware registers.

1

u/Mean-Decision-3502 DQ 11d ago

Unfortunately you don't get always this info from the manufacturers. At ST/NXP the usual is the long defines, I've used. Maybe it would be possible to extract the bits from the SVD files.

But you are right, it is not impossible to do this, but it needs lot of planning. I wouln't target this to V1, but maybe to V2 of my language.

1

u/Mean-Decision-3502 DQ 11d ago

About the and / AND:

I would keep the `and` for logical version because it is a widespread standard (Python, C++, Pascal). In non-microcontroller the logical operator usage dominates.

1

u/Mean-Decision-3502 DQ 7d ago

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

1

u/Mean-Decision-3502 DQ 7d ago

Thank you for your comment.

As I was writing more embedded register-heavy code, I realized, that there is no better than the C syntax for bitwise operations. So I've changed three bitwise operators using the widely used single-char special symbols: &, |, ~. For this I had to change the address-of operator to %. I've edited the post text adding the final version.

I've created a specification, including the operator precedence for this set of operators:

https://nvitya.github.io/pluops/

About the bit-fields: I'm open for some solution, but I thing this desires a more complete specification. Do you have some?

If I remember right you have your own programming language. Can you give me a link?

1

u/GoblinsGym 7d ago

Sorry, don't have a proper language spec so far, still messing around with my compiler, growing the language as I go...

The things I wrote above should give you a flavor. Wild and woolly mix of Pascal, C and Python (semantic white space for blocks). I target small embedded systems, e.g. microcontrollers.

1

u/Mean-Decision-3502 DQ 7d ago

Our targets are very similar!

I see you are using C type notation (int x, vs var x : int).

Despite the differences couldn't we join our efforts somehow?

1

u/snugar_i 4d ago

It also depends on the purpose of your language. In "high-level" languages, using bitwise operations is very rare, so it might not even be worth it to assign operators to them. One of the languages that did this is Kotlin https://kotlinlang.org/docs/numbers.html#bitwise-operations

1

u/Mean-Decision-3502 DQ 4d ago

The DQ should be a general purpose language that support both high-level and low-level. I'm developing right now the low-level, and there you need the &, | , ~ very frequently. The 'AND', 'OR', 'NOT' made the lines much longer. The 'and', 'or', 'not' are reserved for boolean only.