Hi! Have a look at rotateChar in this ROTn cypher:
import strutils
func rotateLetter(c: char, ordZ: int, n: int): char =
var rotatedCode = ord(c) + n
if rotatedCode > ordZ: rotatedCode -= 26
chr(rotatedCode)
func rotateChar(c: char, n: int): char =
const
ordUpperZ = ord('Z')
ordLowerZ = ord('z')
if c.isUpperAscii: rotateLetter(c, ordUpperZ, n)
elif c.isLowerAscii: rotateLetter(c, ordLowerZ, n)
else: c
func rotate*(message: string, n: int): string =
for c in message:
result &= rotateChar(c, n)
The constants ordUpperZ and ordLowerZ are not defined to give meaningful names to pieces of data, but to prevent rotateChar from computing the ASCII codes of "Z" and "z" in each call.
If the code had the expressions as arguments instead, would the compiler optimize that away anyway because the chars would be literals?
case c
of 'A'..'Z': rotateLetter(c, 90, n)
of 'a'..'z': rotateLetter(c, 122, n)
else: c
Switching to ROT26 will also save computations.
Well doing it like this would make it a little slower since you're passing it to a proc at runtime. You can do this:
import strutils
func rotateLetter(c: char, ordZ: static int, n: int): char =
var rotatedCode = ord(c) + n
if rotatedCode > ordZ: rotatedCode -= 26
chr(rotatedCode)
func rotateChar(c: char, n: int): char =
if c.isUpperAscii: rotateLetter(c, ord('Z'), n)
elif c.isLowerAscii: rotateLetter(c, ord('z'), n)
else: c
func rotate*(message: string, n: int): string =
for c in message:
result &= rotateChar(c, n)
This would generate 2 rotateLetter procs, one where ordZ in the code is substituted by ord('Z') and another by ord('z'). Idk if that would be faster but it would definitely add to code size.
Beyond that, ord('Z') is barely a computation, you just convert an integer to another integer. If you still want to make sure it's evaluated at compile time though, instead of doing const ordZ = ord('Z'), use(ordZ) you can just do use(static(ord('Z'))).
That is pretty neat too.
Generally speaking, if there is a standard function that does what I need, I prefer it. Two reasons:
Those are two generic principles of mine, but thanks for the alternative!
ord('Z')
I can not remember a single compiler -- like old pascal on university mainframes with teletype terminal without monitor screen, or Modula and Oberon compilers where it made sense to introduce a named constant for this. Because 'Z' is already a 8 bit integer constant, so the ord() call is basically a nop, it only tells the compiler that we want to use 'Z' as what it internally is, and not as a more abstract character entity. I think even when we call ord() on a runtime variable of type char it is a nop. The ord() function is special in the regard as it is internal to the compiler, which makes me even more optimistic that it is fine even for tinyC compiler.
Unfortunately things are much worse for true proc calls. I have never managed to tell the Nim compiler that it should optimize the second proc call when arguments are unchanged to the first one. There was a forum thread from me about that topic some years ago.
Unfortunately things are much worse for true proc calls. I have never managed to tell the Nim compiler that it should optimize the second proc call when arguments are unchanged to the first one. There was a forum thread from me about that topic some years ago.
You need to tag the proc in C with __attribute__((const)) (via codegendecl)
From ARM docs (but also GCC and Clang)
The const function attribute specifies that a function examines only its arguments, and has no effect except for the return value. That is, the function does not read or modify any global memory. If a function is known to operate only on its arguments then it can be subject to common sub-expression elimination and loop optimizations. This attribute is stricter than __attribute__((pure)) because functions are not permitted to read global memory.
This similar to Nim "no sideeffect"
Closely related is __attribute__((pure))
Many functions have no effects except to return a value, and their return value depends only on the parameters and global variables. Functions of this kind can be subject to data flow analysis and might be eliminated.
Note that those don't know about Nim refs, seq, strings and dynamic memory which are considered not considered side-effect in Nim but should be side-effect in C.