There are several basic rules that should be followed when writing code, in CRPL, or in any other language. This list is sorted in descending order of importance.
#1: Style is optional. It is only ever required when sharing code with others, such as when posting examples on the wiki. There is nothing wrong with creating your own style, either, so long as your team mates agree.
#2: Programs are structured around program flow structures. Use indentation to make them more obvious.
<-food if @eat @drink endif
In the above example, the body of the if is highlighted by being visually indented. The rest of the if wraps around the body, hugging it.
<-food if @eat @drink else @findFood endif
In the above example, it is clear which if that else belongs to. By being of a different indentation, it is automatically visible. The body is split into two bodies, clearly identifying the true case and the false case.
10 0 do I Trace loop ShowTraceLog
The above example demonstrates that these rules are not limited to if statements. Bodies of loop statements should also be highlighted.
#prints a list of primes to the trace log 100 0 do #is I prime I @isPrime if #print I I Trace endif loop ShowTraceLog
The above example demonstrates how to handle nested structures. Nested structures should have their bodies further indented. The above example also demonstrates that comments should be indented as well.
#calculates if a number is prime :isPrime ->num <-num sqrt ->stopPoint 2 ->testFac while <-testFac <-stopPoint lt repeat <-num <-testFac mod eq0 if FALSE return endif <-testFac 1 add ->testFac endwhile TRUE
The above example demonstrates that indentation also applies to function definitions.
#prints a list of primes to the trace log 100 0 do I @isPrime if I Trace else endif #print I if it is prime loop ShowTraceLog
The above example demonstrates inlining. If a program flow structure and both bodies can fit on a single line, it is permissible to do so. In such a case, indentation rules are impossible to apply. Comments can also be inlined.
Two spaces is the standard indentation, but four spaces is a popular alternative. Using tab indentation is less common, usually reserved for contexts where the tab distance can be reassigned from the standard roughly eight spaces. Using three spaces is bad.
Sometimes, authors include the terminating symbols (endwhile, loop, endif) in the body, thus indenting them. When using this style, always put a blank line after such symbols to further distinguish them.
#3 Max line length is 80 characters, line continuations should be doubly indented.
@overlyLongFunctionNameIDidNotThinkThrough @anotherOverlyLongFunctionNameIDidNotThinkThrough and if @eat endif
The above example shows a line that would have been too long if not cut in two. The double indenting helps to visually distinguish the second half of the line.
As CRPL is a stack language, there is no clear start or end of a line of code. A general rule of thumb is if the stack is empty, the code is at a line break. If there is still something on the stack, it is still the same line.
The 80 character limit hearkens back to the early days of programming on DOS computers, where the screen width was 80 characters. On modern wide screens, you can probably get away with 120 characters or more, but seeing all the code in a single thin column helps with readability, and allows two windows to be open at the same time, a code window, and anything else, split screen style.
Further extra indentation may be a good idea, but be consistent.
#3 Blank lines separate paragraphs.
There are tiers to this rule. The highest tier is separating classes of functions in a really large file:
:func1 <body> :func2 <body> :func3 <body> ######################separator###################################### #section header :func4 <body> :func5 <body> :func6 <body>
The second tier is a more casual sectioning for smaller files:
:func1 <body> :func2 <body> :func3 <body> :func4 <body> :func5 <body> :func6 <body>
Notice the three blank lines.
The third tier is a single blank line. A blank line before each function is mandatory. Complex functions will also need to be separated into paragraphs:
#Calculates if a number is prime. :isPrime ->num <-num sqrt ->stopPoint 2 ->testFac while <-testFac <-stopPoint lt repeat <-num <-testFac mod eq0 if FALSE return endif <-testFac 1 add ->testFac endwhile TRUE
A rule of thumb is there are one to five lines in a paragraph. Each paragraph must have its own story, the lines in the paragraph united in a single mission. An optional practice is to place a comment at the head of each paragraph, explicitly stating this common purpose:
#calculates if a number is prime :isPrime #initialize variables ->num <-num sqrt ->stopPoint 2 ->testFac while <-testFac <-stopPoint lt repeat #test the number against a factor <-num <-testFac mod eq0 if FALSE return endif #change test factors <-testFac 1 add ->testFac endwhile #if no factors match, this number is prime TRUE
Grouping program flow structures is a sticky point. For short bodies, the entire structure may be one paragraph, or even part of a larger paragraph. If the body is large, is it enough to split the body in two, or should the body be separated from the program flow structure? If the body is separated from the program flow structure, does the program flow structure still need to be separated from the previous and next paragraphs? If the program flow structure is grouped with the body, does the paragraph comment go inside the body or outside the body? A rule of thumb is the more complex the program flow structure, the more blank lines should be inserted.
#damages creeper in a square :damageCreeperSquare #load arguments ->r ->y0 ->x0 #in a box shaped region <-r 1 add 0 <-r sub do <-r 1 add 0 <-r sub do #remove one creeper I <-x0 add J <-y0 dup2 add GetCreeper 1 sub 0 max SetCreeper loop loop
It is common practice to group nested loops together, but always separate the body with whitespace and isolate from surrounding paragraphs.
A rule of thumb is a function is composed of one to five paragraphs. Any more, and it should probably be broken down into multiple functions.
A blank line should always be inserted at the very end of a code file.
#4 There are three different types of things. Things with ALL_CAPS names, things with CapitalizedNames, and things with normalNames.
In many languages, there are clear rules about what should and should not be capitalized. ALL_CAPS is usually reserved for constants, and CapitalizedNames are usually reserved for classes and structures. As CRPL does not have classes and structures, CapitalizedNames can be used for other things, such as $PublicVariables or @FunctionNames. built in functions and constants are not case sensitive, so you can interperet them however you want. The important thing is to be consistent.
#Circler.crpl $Speed:1.0 $TurnRate:0.1 <-angle <-TurnRate add <-PI2MUL mod <-PI2MUL add <-PI2MUL mod ->angle self "main" <-angle SetImageRotation CurrentPixelCoords ->y ->x <-Speed 8 mul <-angle sin mul <-y add ->y <-Speed 8 mul <-angle cos mul <-x add ->x Self CONST_PIXELCOORDX <-x SetUnitAttribute Self CONST_PIXELCOORDY <-y SetUnitAttribute :awake once PI 2 mul ->PI2MUL 0 ->angle endonce
All examples here choose to capitalize globals, but not user defined functions, and stick to the capitalization of builtins as they appear in the CRPL reference.
Unless you are programming in an ancient language, code file names should always be capitalized. Be careful with changing the capitalization of an existing file on windows, as windows filesystem is not case sensitive, but CW3 is, so the file will get perpetually overwritten unless you delete it from your scenario first. Always make backup copies.
#5 the length of a variable or constant name should be inversely proportional to how often it is used.
Loop variables see heavy use inside loops, so one letter names for them are common. The builtin I J and K enforce this principle. Function names are not likely to be used as much. Descriptive two or three word names for functions are typical. The more often a function is used, the more you can get away with a one word name.
The concept of scope is relevant here. $Global variables can be seen from different CRPL cores and scripts. This gives a larger scope. Per line of code, the likelihood that any global is used is pretty low, so give them large names. Variables relevant to only one function, however, have a small scope. Per line of code, such variables have a high chance of being used, so shorter names are reasonable.
Be warned: all local variables in CRPL are actually file global, so reusing names between functions is dangerous.
#6 Read code with a fixed width font. This ensures that what you see is common across different machines and contexts. If you add examples to the wiki, surround with <code> <\code> and the example will automatically be displayed in fixed width. Monospace, mono, and console are common words associated with fixed width fonts.
#7 Any paragraph or line or even subsection of a line that occurs more than once might make a good function. Go through your code and look for patterns and repetition at various scales. If you spot two very similar paragraphs think about how they would need to change to become identical. If it is simple, consider making a new function. The more often it occurs, the cleaner function extraction will make the code.
#8 Precalculate stuff. Go through your code looking for patterns or repetition at various scales. If you calculate the same number twice, consider calculating it earlier, and putting it in a variable. In addition, if you pull a calculation out of a loop, the code usually becomes faster.
Too much indenting can squish the code to the right and hinder readability. In addition to putting the body of a heavily nested loop into its own function, there is a special rule that can be used.
If else chains:
#play sound: silence 50%probability # weapons15 33%probability # explosion4 11%probability # weapons18 6%probability 0 18 RandInt ->temp <-temp eq0 if "Weapons18" PlaySound else <-temp 3 lt if "Explosion4" PlaySound else <-temp 9 lt if "Weapons15" PlaySound endif endif endif endif
This is similar to case statements available in other languages and elseif statements in yet others.