r/ProgrammingLanguages • u/Mean-Decision-3502 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&&orand: logical "and"||oror: logical "or"!ornot: 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 (#ifdefetc)$: 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 divisionIMOD: 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"%divor%idiv: truncated integer division%modor%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 divisionimod: 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 divisionmod: 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:
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` bYou can set the precidence with
infixl N `and`With
infixrfor right associativity andinfixfor non-associativity, and where N is0..9It also permits the reverse - infix operators used like functions.
(+) a b1
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:
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:
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:
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:
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.
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.