Parse this sic
Parse this sic is an esoteric programming language by User:Digital Hunter. The name has a cute double meaning, and also serves to continue the pattern of languages I've created that end with "-ic".
If there's anything that needs clarification please bring it up on the talk page. If there are any glaring errors use your best judgement on how to clear them up.
- 1 Language overview
- 2 Commands and keywords
- 3 More on words
- 4 Parenthetical expressions
- 5 Numbers
- 6 Example programs
- 6.1 Hello, world!
- 6.2 Cat program
- 6.3 Reverse cat
- 6.4 Infinite loop
- 6.5 Factorial
- 6.6 Fibonacci numbers
- 6.7 Truth-machines
- 6.8 Digital root
- 6.9 Prime number generator
- 6.10 Pi calculator
- 6.11 Phi calculator
- 6.12 Euler's number calculator
- 6.13 FizzBuzz
- 6.14 99 bottles of beer
- 6.15 Deadfish interpreter
- 7 Info to come
Parse this sic, or PTS, is a stack-based language with self-modifying code, built on the premise of the "word" datatype. Each word is essentially a string (but calling them strings is cursed and illegal) with an attached numeric value, and from this point forth words will be described with double quotes (as any string would). Anything that evaluates to a word can be passed to a parenthetical expression, and is known as a label. How exactly words' values are determined will be described in detail later. The null word is a special word that looks like an empty string, and is the shortest word with a value of zero.
Whitespace is never ignored, and words can and probably will contain interesting whitespace characters that an implementation must handle neatly. Additionally, whitespace contributes to the character count of a program which will matter to you in a few minutes if you're reading this for the first time.
Certain expressions will evaluate to a number, which is the natural result of performing arithmetic. The program itself will keep track of the correct value with a word that suffices, and the programmer can assume that any given number will always be mapped to the same representative word. A good implementation would store all numbers as the shortest word with the correct value. A list of the shortest words with specific values will eventually be provided. To either eliminate or espouse your confusion, this page will be written with normal base-10 numerals unless otherwise specified, and with the hope that you don't think too hard about the fact that every base is base-10.
Every character in your source code is given an index, with the first given index 1 and the last given index 0. Indices increase from left to right by one per character, and wrap around the edges. For example, in
= at the very end has indices 0, 9, -9, 18, -18, and I think you can see the pattern. Yes, it does get annoying to keep track of indices in long programs where the code that needs indices changes the length of all the code and you have a lot of things all depending on each other. No, nobody's doing anything about it.
Character indices wrap because code execution wraps around the edges. A program without a proper terminating case will thus repeat indefinitely.
Commands and keywords
Commands are commands, and keywords are special words that change the effects of a three-parameter parenthetical. This is a basic overview of their properties.
|Command or keyword||Description|
||Pops a word off the stack and jumps to the character in the program matching the index of the word's value. Technically evaluates to that word but this is nearly unusable. Is NOP if stack is empty.|
||Flips the code reading direction between LTR (default) and RTL. Also flips the indices of every character. Technically evaluates to the null word but this is nearly unusable. Parentheses pair the same way regardless of code direction; a single parenthesis can act as either an open or a close.|
||Pops a word off the stack and evaluates to it as a label if inside a parenthetical, outputs the word otherwise. Is NOP if stack is empty.|
||If the code direction is LTR and the value of the word on top of the stack is less than 1, skip to past the next |
If the code direction is RTL and the value of the word on top of the stack is greater than 0, skip to past the next
Technically evaluates to the word peeked at but this is nearly unusable.
||Tells the program to stop reading a word. Creates a label to that word to which the whole bit (as in bunch) evaluates.|
||Skips to past the next |
||Kills program execution.|
||Parenthetical expressions come in four flavours: zero-parameter, one-parameter, two-parameter, and three-parameter. They will be described more clearly later.|
|Anything else||Activates program word-reading.|
||When the first parameter in a three-param evaluates to "ameliorate" exactly, the parenthetical evaluates to a word with the value of the sum of the other two parameters.|
||Same deal as above; evaluates to the third subtracted from the second.|
||Same deal as above; evaluates to the product of the other two.|
||Same deal as above; evaluates to the integer quotient where the second is the dividend and the third is the divisor. If the third has a value of zero, evaluates to the null word.|
||Same deal as above; the second parameter is interpreted as a PTS program operating on the same stack as the main program, but with independent indices. All zero-params immediately evaluate to the third parameter. The whole expression evaluates to the concatenation of everything output from the mini-program through |
||Same deal as above; the first (determined by code direction) instance after the closing parenthesis of this expression of a substring of the code that matches the second parameter exactly will be replaced with the third parameter, with the search wrapping around the edges. If no match is found, evaluate to the null word. If a replacement is made, evaluate to the word that was replaced (the second parameter).|
||Same deal as above; evaluates to the second parameter if the second and third parameter match. Evaluates to the null word otherwise.|
A neat 7-command 7-keyword situation.
More on words
Words are neat because they serve the function of both a string and a number. Note: I put number in italics when referring to numbers, those interesting technical words that result from an arithmetic three-param.
When the time comes to determine the value of a word, its equivalent string is searched through character-by-character from the beginning until a valid digit is encountred. A digit qualifies as any
A..Z character (case-sensitive), and the first digit found will determine the base in which the rest of the word is read as a number -- 0 indicates unary, 1 indicates binary, Z indicates uhh base-36. Every nondigit will be ignored when calculating the value of the word from then on.
For example, the word
"140f9ai392(324" has a value equal to 2 (with or without the quotes!), because the word is read as the binary number 10. A nice consequence of PTS's number system is that both "0" and "1" have the same value.
In code, words are generally first created with a word literal. When the program is run, if a non-command character is encountred it is treated as the first character in a word and word-reading mode is activated. Every character read from then on is added to the word, until
& is found. This ends the word reading and creates a label to the word that was just read, which can then be passed to a parenthetical or just discarded (but why would you ever want to do that? So cruel). For example,
will act as a label to the word
"140f9ai392(324" that is destined to do many great things. Do ignore the fact that this label is immediately discarded. Other than wasting memory, however, floating labels are helpful for word values that may be used later, or for placeholders with the
"succeed" functionality. Additionally, marking comments and whitespace for readability with
& will likely be used in larger programs so an implementation should be able to handle it effectively.
At long last.
First comes the zero-param.
() is the sole example. When encountred in a program ordinarily, the user will be prompted for an input, which is automatically cast as a word. The zero-param will then evaluate to that word if passed to an outside parenthetical. However, in a
"walking" mini-program, zero-params will not prompt for input but instead evaluate to the third parameter from the outside program as described earlier.
Next comes the one-param.
(one-param&) is a valid one-param, with the word
"one-param" as its one parameter. Anything that evaluates to a word can be passed to a one-param. A one-param's sole purpose is to push its parameter onto the stack, but it also evaluates to that word and can be used to do many things. It can even be passed to itself, like
((darkness&)), and push the same word twice!
Third comes the two-param.
(1f1sh&2f0sh&) is a valid two-param, where
"2f0sh" are its parameters. A two-param sees the entire program as one long (and technically infinite) word, and evaluates to the word that is the substring marked off with the indices determined by the values of its parameters, inclusive. This was incredibly wordy, so here's an example.
This code first creates a label to the word
"uncopyrightable" which is immediately discarded. Sorry. Anyway, the first two-param looks at character 3 and character 6 to evaluate to
"copy" and then also be immediately discarded. The next two-param evaluates to
"bath". The final two both look from the very first (value 1) to the very last (value 0 (the second
& labels the null word)) characters, and both evaluate to
"uncopyrightable&(3&6&)(D&A&)(1&&)(0&&)=" which is the entire program. This makes it very easy to create pretty lame quines, because the source code can always be directly accessed by PTS programs, but interesting/traditional quines can still be written with other means.
At even longer last, the three-param.
(perowanfe&word1&word2&) is a valid three-param. But wait,
"perowanfe" isn't one of the keywords listed with the handy acronym Astute Dinosaur Thinks Stars Want Some Dinner! This is fine, because the three-param combines the functionality of functions with the equivalent of variable assignment and also word concatenation. Three uses for the three-param.
Above were described the effects of three-params if their first parameter evaluates to one of the keywords. In the example
(perowanfe&word1&word2&), however, the second and third uses of the three-param are demostrated concurrently. From this point on in the program, anything that evaluates to
"perowanfe" such as
perowanfe& will instead evaluate to
"word1word2" -- unless it is the first parameter of a three-param in which case the three-param will act to reassign its value -- hence the concatenative ability and pseudo-variable assignment. Some examples assuming these examples were already executed somewhere prior:
(perowanfe&conch&shell&) & Anything that evaluates to "perowanfe" now evaluates to "conchshell" (instead of "word1word2") & (word1word2&conch&shell&) & Anything that evaluates to either "perowanfe" or "word1word2" now evaluates to "conchshell" & (variable&New_Value&&) & Anything that evaluates to "variable" now points to "New_Value" &
Parenthetical expressions can (and will) be nested, and are evaluated in the order of code direction, deepest first; if the code is LTR then the program will "enter" any parentheticals it sees from the left, and go as deep as possible. This matters for, say, the effects of stack manipulation. Parentheses pair the same way regardless of code direction -- any given
( could act as an open or a close.
It is specified that it is unspecified how any program will represent internally any given number that isn't 0 (by the null word) or 2..35 (by the digits 2..Z), and any programs written in PTS must assume that numbers other than those 35 cannot be trusted (for output purposes). Note: 1 is not included in those safe numbers because both
"1" are valid words with value 1 as mentioned above somewhere.
To argue this reasoning, take for example the number -6 (negative six). A PTS snippet such as
(dominate&3&9&) will evaluate to that number -6, but because there is no word with value -6 the number does not exist. It can still be pushed to the stack and interacted with (though outputting it or any other number other than the safe numbers is undefined behaviour), but in your mind it should appear as a black indecipherable fog. This is how all numbers should be treated in PTS (again, other than the safe ones).
However, because these numbers obey all the same mathematical rules as the numbers you're used to, "conversion" of them to a word that when read in decimal is that number corresponding to the value of the number is possible with a simple base-conversion algorithm. In PTS, this is most readily achieved with a snippet of code that may be executed via
"walking". For example:
((*))( &&&)/(S&)+/(&*)( &)=((dominate&(*)(times&(spaces&(*)A&)A&)))/(413&)+/( &0& &)(&*)((spaces&*A&))(C&)+((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(C&)+/(**)( &1& &)((spaces&*A&))(C&)+-
is a simple (bi-directional!) algorithm whose purpose is to leave the element currently on top of the stack untouched but push a new word that reads as a base-A (base ten) numeral and then terminate (and return control to the part of the program that executed it with
"walking"). It is equivalent to the following (really bad) pseudocode:
duplicate the top of the stack set a variable (newline) to the null word if the top of the stack > 0 push the number that is (current top of stack) mod (A) if this new top of stack is > 0 if it is > 1 (checks by pushing the top of the stack - 1) set the newline variable to the concatenation of (this top of stack digit) and (its prior value) discard the two extraneous top values replace the top with it integer-divided by A jump back to the very first if-check otherwise (it is equal to 1) set the newline variable to the concatenation of (the word "1") and (its prior value) discard the two extraneous top values replace the top with it integer-divided by A jump back to the very first if-check otherwise (it is equal to 0) set the newline variable to the concatenation of (the word "0") and (its prior value) discard the single extraneous top value replace the top with it integer-divided by A jump back to the very first if-check otherwise (it is zero and the process is done) discard the top of the stack (zero) push the newline pseudo-variable's value to the stack kill execution
a % b is evaluated with
a - (b * (a / b)) if
/ means integer division, because PTS does not have a built-in modulo operator.
A simple function-ish like this one is essential for any PTS program with meaningful numerical output, though it should be noted that this particular function is intended for use in a setting where negative numbers won't appear. Note that this little algorithm will work (as long as the bounds are correct) regardless of the code direction when it is run with
"walking" (the side-effect of swapping code direction does not last).
A similar function with the reverse effect can be written as well, interpreting the word on top of the stack as a decimal number that is converted to a (unspecified) PTS-compatible form:
-(p&&(dominate&p&1&))((ditto&(p&p&)H&))/(&*)( &)=/(&*)( &&(times&A& &))(2&)((ditto&(p&p&)0&))/(&*)+/( &*(ameliorate&(p&p&) &))+((*&%&deeccus))(&&& )(&1&&p)&H%
The most effective (and only) way to use some of PTS's commands is using numbers, however, so it is essential that you can come up with words with the right value on the fly. You can find a list here of numbers that you can use as a lookup table of sorts when writing PTS programs.
These examples will eventually be followed up with explanations and prefaced with the knowledge required to understand them and hopefully debug them.
The simplest one pushes the word
"Hello, world!" to the stack to be outputted.
Quite a fun one:
((Saying&the following&))*=Hello, world!
The R-th character just so happens to be the
"Saying" points to the first character after that (the S-th character is
H here) and
"the following" points to the last character.
This one will prompt the user for word (basically string) input indefinitely.
Input is taken, pushed to the stack, and outputted, and program execution wraps around to do it all again. Adding a
= to the end will make it a simple terminating cat.
Takes an input and reprints it reversed, demonstrating the basic principle behind .. a non-terminating example:
Note that the
"succeed" is never called in the main program; the side effect of replacing the
% with the input is therefore limited only to each instance of that code being called with
"walking". Because I'm silly, I wrote this code with the intention of it being RTL without realising that it works just fine (and identically) LTR.
While any PTS program without a
= is trivially an infinite loop, here's one infinitely-executed program that neither reaches the end nor has any
A good implementation will crash upon trying to run it (the program length doubles with each iteration).
Here's a similar program that will lead to some kind of recursion error:
Prompts the user for input of something whose value will be evaluated in the normal PTS way (use unary or binary input to be safe), and outputs whatever the implementation decides is the best way to represent the number that is the input's factorial.
For example, inputting
"5" could output:
"AA"-- best implementation!
"00000.."(120 times) -- ☃
That example uses recursion which prevents it from involving a convert-to-base-A segment easily while still showcasing the use of PTS's builtins "intuitively", but it's still a neat little program that does the right job by basically assuming that it does the job of outputting the factorial and simply passing the torch to a
"walking" call of itself to find the factorial of one less than its own job, with the base case of 0! = 1.
The following solution does output the base-2 represented factorial of the input, with a simple convert-to-base-2 operation built right into the code:
(Input a number to be factorialed below. Note this needs a PTS number so use binary or unary to be safe! Input: &)*((( &&())))(Factorial of &)**( in binary is: &)* ^ This'll take an input, use newline as our "counter", and also pushes it to the stack Now, we're going to push each number less than that input onto the stack until we reach 0 Something like  becomes [5 4 3 2 1 0] &/((dominate&(*)1&))(J6&)+/(Then the top of the stack is cleared &*) and the goal now is to perform a loop where the top two stack values are popped and their product is pushed. By decrementing the value in our counter and checking that it stays above zero, this sort of loop functionality is achieved! &(( &&(dominate& &1&)))/(&*)((times&**))(845&)+ <-- this is just that multiplying step /( Now, we have the completed factorial on top of the stack as intended. The only problem here is that it's in the form of a PTS "number"! The next step is just to convert it to a consistent form, and I've chosen to stick with using binary (I feel like unary would end the universe) &*)( &&&)/|/( &)*( &)*= That bit of code there is the end condition, just output whatever's in our new use of newline as our binary word placeholder. We push to the stack our factorial modulo 2, which conveniently in PTS logic falls on both ends of the slash command (technically we use ditto instead of dominate because there are only two cases in binary and it's just slightly shorter) |((ditto&(*)(times&(spaces&(*)2&)2&)))/( &0& &)| if they're the same, put a 0 in front of our placeholder concatenation thingy and then jump to the final step /( &1& &) if they're not the same then put a 1 in front of our placeholder and continue to the final step | <-- this is a "universal landing pad" sort of structure! --> &(&*)((spaces&* divide by 2 for the next iteration ... &))(WD ... and jump back to repeat --> &)+ until the top of the stack is zero and the base conversion is complete
Here's the same program, minimised:
(( &&()))/((dominate&(*)1&))(A&)+/(&*)(( &&(dominate& &1&)))/(&*)((times&**))(100111&)+/(&*)/(413&)+/( &)*=((ditto&(*)(times&(spaces&(*)2&)2&)))/( &0& &)|/( &1& &)_|&(&*)((spaces&*2&))(93&)+
Unary and binary are the only two bases that have a valid in-PTS representation for every number, and because this particular program is single-input single-output it can conveniently be plugged into any larger program to be run with
"walking" to produce a meaningful value. If used in such a way, it may be more efficient to operate directly on the top of the stack, and this can be done by simply replacing the
() at the start with a
* and deleting the
* in the middle by the
The following prompts the user for "n" (interpreted as a PTS number -- unary or binary to be safe) and outputs the nth Fibonacci number (in decimal!), if the 0th and 1st are 0 and 1, and each next term is the sum of the previous two.
(&)(1&)(( &&(dominate&()1&)))/(&*)(82&)+/((ameliorate&*1&))/(1&)*=/(0&)*=( &)( &&(dominate& &1&))/(&*)(walking&(C0&DB_&)&)(82&)+/(&*)(walking&(DC_&&)&)*= &(t&&*)(b&&(ameliorate&t&(*)))(t&)(b&)=( &&&)/(K&)+/*( &)=((dominate&(*)(times&(spaces&(*)A&)A&)))/(96&)+/*( &0& &)((spaces&*A&))(7&)+((dominate&(*)1&))/*( &* &)((spaces&*A&))(7&)+/**( &1& &)((spaces&*A&))(7&)+
If the user inputs "9" or "1001" or "000000000", the program will output "34".
There is a shorter-by-one-char version that does the same job but is simply less interesting:
On account of the fact that it's "straightforward" to see what's going on.
Contrary to what is said on the digital root calculator page, such a program in PTS is less than easy due to its lack of decimal bias.
The following program is really 3 "functions" and a master program in disguise: one to interpret a decimal input, one that converts valid PTS values to a decimal-readable form, and one that finds the sum of the digits of a supplied (assumed) decimal value.
-|( &&&)/(K&)+/*( &)=((dominate&(*)(times&(spaces&(*)A&)A&)))/(96&)+/*( &0& &)((spaces&*A&))(7&)+((dominate&(*)1&))/*( &* &)((spaces&*A&))(7&)+/**( &1& &)((spaces&*A&))(7&)+-((( &&(dominate& &1&)) &))((ditto&(*)H&))/**=/*((ditto&(*)0&))/**(2&)+/*((ameliorate&**))(2&)+(((*&%&deeccus)))(&1&& )&H%|( &&(dominate& &1&))((ditto&( & &)H&))/(&*)(K4&)+/(&*)((times&*A&))((ditto&( & &)0&))/(&*)(2&)+/(&*)((ameliorate&*( & &)))(2&)+((dominate&(*)9&))/(&*)(walking&(3&&)&)(walking&(20110&601&)&)(K4&)+/(&*)*=+(((()&$&deeccus)))(&1&& )&H$
It essentially works the brute-force way, repeatedly first converting the value on top of the stack to decimal and then summing the digits until it is less than 10.
1+(n-1)%9 trick makes a much shorter program:
-( &&(dominate& &1&))((ditto&( & &)H&))/(&*)|/*((times&*A&))((ditto&( & &)0&))/(&*)(2&)+/*((ameliorate&*( & &)))(2&)+|((dominate&*1 &))((dominate&(*)(times&(spaces&*9&)9 &)))/((ameliorate&*1&))*=*(&1)/- +(((()&%&deeccus)))(&1&& )&H%
Note that there are 3 and only 3 newline ('\n') characters in the above that are there for aesthetics; it will be left as an exercise to the reader to determine which three.
Prime number generator
Takes a PTS number input and lists out (in decimal) all the prime numbers up to and including that input using a sort of Sieve of Eratosthenes approach. Output is separated by
" " characters.
(())(A7&)+| (&)(|/|/(411&)+|((dominate&(*)(times& &(spaces&(*) &))))/(&*)(succeed& (&(c&(2&6&)*))|/(**)_|&(8&)+ (&)=/((dominate&(*)1&))(A7&)+/(**)/( &&*)(walking&(B&A6&)&)( &)(walking&(E3&&)&)*( &)*(10010011&)+/=( &&&)/|/*( &)=|((dominate&(*)(times&(spaces&(*)A&)A&)))/|/*( &0& &)((spaces&*A&))(7&)+|((dominate&(*)1&))/*( &* &)((spaces&*A&))(7&)+/**( &1& &)((spaces&*A&))(7&)+
The difficult part of implementing an eratosthesieve in PTS is that the stack cannot be looked through trivially; this hurdle is overcome by making use of PTS code's ability to edit itself. As the stack is read back in the main logic-dense part of this particular program, stack elements that survive the sieve are popped while some code that comes later is modified to have extra one-params that'll push everything back onto the stack (so deeper elements can be tested as well).
Takes a PTS number input and outputs some decimal approximation to pi (3.14159265...) whose accuracy vaguely corresponds to the input.
(())( &&8&)/((dominate&*1&))( &&(times&A& &))(C&)+/(c&& &)( &&&)((ameliorate&(spaces&c&(times&(ameliorate&(times& &4&)1&)(ameliorate&3&(times&4& &))))*))((spaces&c&(times&(ameliorate&(times& &4&)1&)(ameliorate&3&(times&4& &)))))/(&*)( &&(ameliorate&1& &))(2102&)+ /( &*&)/|/(&*)( &)*=|((dominate&(*)(times&(spaces&(*)A&)A&)))/|/(&*)( &0& &)((spaces&*A&))(G0&)+|((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(G0&)+/(**)( &1& &)((spaces&*A&))(G0&)+
For example, inputting
"313837". Execution takes about A times longer for each increment of whatever's input.
This particular program uses Leibniz's approximation of pi, a special case of the inverse tangent formula.
Takes a PTS number input and outputs some decimal approximation to phi (1.6180339887...) whose accuracy vaguely corresponds to the input.
((c&&()))(D5&)+( &&(dominate&*1&))(&)(1&)( &)/*|/((ameliorate&*1&))/(1&)=/(0&)=|( &)( &&(dominate& &1&))/*(walking&(B3&&)&)(2110&)+/*(t&&*)(&*)(t&)= (t&&*)(b&&(ameliorate&t&*))(t&)(b&)= (walking&(G&&)&)((ameliorate&c&1&))(walking&(G&&)&)( &&1&)(c&)/(&*)(c&&(dominate&c&2&))( &&(times&A& &))(F5&)+/(&*)((spaces&(times&* &)*))( &&&)/|/(&*)( &)*=|((dominate&(*)(times&(spaces&(*)A&)A&)))/|/(&*)( &0& &)((spaces&*A&))(652&)+|((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(652&)+/(**)( &1& &)((spaces&*A&))(652&)+
For example, inputting
"161764". Unlike the pi calculator, this one handles inputs as large as
This particular program determines phi as the ratio of consecutive (big) Fibonacci numbers.
Euler's number calculator
Takes a PTS number input and outputs some decimal approximation to e (2.718281828...) whose accuracy vaguely corresponds to the input.
(i&&())(c&&( &&1&))(i&)/(&*)(1&)(1&)(1&)((times& &2&))(1&)(1&)(c&&(times&V8&c&))( &&(ameliorate&1& &))(i&&(dominate&i&1&))(K&)+/(&*)(t&&(d&&*))(n&&*) &/(d&&(ameliorate&n&(times&d&*)))(n&&(times&t&*))(t&&d&)(10011000&)+ /((spaces&(times&(ameliorate&(times&d&2&)n&)c&)d&))( &&&)/|/(&*)( &)*=|((dominate&(*)(times&(spaces&(*)A&)A&)))/|/(&*)( &0& &)((spaces&*A&))(G5&)+|((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(G5&)+/(**)( &1& &)((spaces&*A&))(G5&)+
For example, inputting
This particular program generates a truncated simple continued fraction representation of e on the stack which is simplified for an approximation.
Loops FizzBuzz from 1 to
"410" which is 105 in decimal (so it can finish with a FizzBuzz). The relatively complicated
"walking" call is written so that the
"410" can be changed to any value the user wishes, regardless of length, with no consequences.
(&)((ditto&(*)(times&410&1&)))/=/(&*)(((ameliorate&*1&)))(walking&((dominate&&H9&)&)&)*( &)*(4&)+( &&&)((dominate&(*)(times&(spaces&(*)3&)3&)))/(2111&)+/( &&Fizz&)*((dominate&(*)(times&(spaces&(*)5&)5&)))/(B0&)+/( & &Buzz&)|hey|*( &)/**( &)=/*( &&&)hi&/|/*( &)=|((dominate&(*)(times&(spaces&(*)A&)A&)))/|/*( &0& &)((spaces&*A&))(C0&)+|((dominate&(*)1&))/*( &* &)((spaces&*A&))(C0&)+/**( &1& &)((spaces&*A&))(C0&)+
Note the trailing space (' ') at the end of the first line; some interpreters (such as the one this author is using) will incorrectly handle the side effect of redefining newline ('\n') that should be limited to only the
"walking" call (which leads to a messy doubleprinting scenario) and the cleanest solution is just to alter the printed separator invisibly.
This one prints all ten verses of 9 Bottles of Beer with the typo "1 bottles of beer" because I'm lazy. A full program will follow, but this shorter example demonstrates the basic logic of a 99 bottles program. Note the newlines and the double newline in the middle. Necessary for proper spacing of the verses and changing this aesthetically will also require label changes.
-(No&)*((3231&544&))*(No&)*((3231&EC&))*(! Go to the store, buy some more, &)*(9&)*((3231&550&))*(.&)*=+(&5)*(& .)*((&36&I))*/*(&oN)/(((&0*&etanimod)))*(& ,dnuora ti ssap ,nwod eno ekaT !)*((&X&I))*((*))*(& ,llaw eht no reeb fo selttob )*((*))/+(&)/(&9)
This particular program could be edited quite easily to almost be a proper 99, but runs into the particular problem that numbers' representations are unspecified. Even if they were specified, by the rules of PTS a decimal-biased song wouldn't be possible anyway -- without a base conversion function.
Despite its shortcomings, it's a good example of PTS's flow control, and most prominently the changing behaviour of
/ when reversed.
If the bulk of the program was written for LTR, changing the decision portions isn't difficult but adds length to the program which isn't great.
With all that in mind, this is a full 99 Bottles of Beer, complete with base conversion and good grammar.
(L4&)+ 99 bottles of beer written in Parse-this-sic =*(&.)*((&3G&8F))*(&99 ,erom emos yub ,erots eht ot oG !)*((&111000001&8F))*(&oN)*((&5G&8F))*(&oN .)*((&3G&8F))*(&oN)*((&AI&EH ))*(&reeb fo elttob 1)*((&5G&000000001))*(&elttob 1)(*&)[h]+(&525)*(& .)*((&3G&8F))*(&(&1&7D )&gniklaw)((&1*&etanimod))*(& ,dnuora ti ssap ,nwod eno ekaT !)*((&111000001&8F))*(&(&1&7D )&gniklaw)*(& ,llaw eht no reeb fo selttob )*(&(&1&7D )&gniklaw)(*&)/+(&0K)/((&1(*)&etanimod))(&99)&-((*))( &&&)/(S&)+/(&*)( &)=((dominate&(*)(times&(spaces&(*)A&)A&)))/(413&)+/(&*)( &0& &)((spaces&*A&))(C&)+((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(C&)+/(**)( &1& &)((spaces&*A&))(C&)+-
For readability, some of my two-params have a newline in one of their bound words that doesn't affect the value. Newlines and whitespace can always be interspersed in nonprinting words, though using spaces for character padding to make index counting easier is discouraged. Note the small
[h] hidden in the author's code that is in direct violation of the discouragement expressed in the previous sentence.
Prompts the user for input of a one-line idso Deadfish program. Note: this one doesn't seem to like outputting "1".
-|( &&&)/(N&)+/(&*)( &)=((dominate&(*)(times&(spaces&(*)A&)A&)))/(403&)+/( &0& &)(&*)((spaces&*A&))(7&)+((dominate&(*)1&))/(&*)( &* &)((spaces&*A&))(7&)+/(**)( &1& &)((spaces&*A&))(7&)+|((ditto& &(ameliorate&FF&1&)))/( &&&)/((ameliorate& &1&))/(100001001&)+/( &&&)(**)(p&&(dominate&p&1&))((ditto&(p&p&)i&))/(&*)( &&(ameliorate&1& &))(2&)+/(&*)((ditto&(p&p&)d&))/(&*)( &&(dominate& &1&))(2&)+/(&*)((ditto&(p&p&)s&))/(&*)( &&(times& & &))(2&)+/(&*)((ditto&(p&p&)o&))/(&*)( &)(walking&(3&D1&)&)*( &)*(2&)+/( &*)((ditto&(p&p&)H&))/=/(&*)(2 &)+((()&%&deeccus))(&&& )(&1&&p)(&O&&o)(&S&&s)(&D&&d)(&I&&i)&H%
Because this is the PTS wiki page, the author of this program will provide an explanation for it, with the convention that RTL code is shown with the wrong parenthesis types for readability.
Program execution hits the
- at the start to read RTL from the very end. The relevant part of the program now is
%H&(i&&I&)(d&&D&)(s&&S&)(o&&O&)(p&&1&)( &&&)((succeed&%&()))+[ et cetera ]
% is not a command and so starts word-reading, ended by
&. The first four three-params make it so that any word evaluating to one of idso will point to their capitalised variant, with the unfortunate side-effect that this interpreter accepts case-insensitive Deadfish, but this step is important because
"ditto" cannot do anything useful if the words it compares have zero value (like lowercase letters). The next three-param establishes
"p" as a pointer of sorts, keeping track of which Deadfish instruction is next to be executed. The last three-param initialises the newline to act as this program's accumulator "variable", that will keep track of the Deadfish program's accumulator. The
H will mark the end of the Deadfish program later.
We're left with
((succeed&%&()))+. There's a one-param that will push the value of
(succeed&%&()) to the stack; this three-param looks through the entire program for the first
% to replace with user input. This just so happens to be the
% from the very start, so if the user inputs
"diissiso" the snippet looks now like
which acts to store the Deadfish code safely at the very end of the main program (remember that this snippet is backward).
(succeed&%&()) evaluates to
"%" which is pushed to the stack,
+ pops it and sees its value as zero, sending the program back to the end (/start)
Program execution is sent back to LTR, and the code from indices
"D1" is conveniently skipped over by
| <- these -> |. Note that this skipped code is in fact quite similar to the base-conversion-of-the-top-of-stack function showcased earlier on this page. The rest of the PTS program is quite straightforward, if the reader may first accept that each "check" pushes its value to the stack to be peeked by
/ and this value is immediately discarded to keep the stack clean:
- Check if the accumulator's number matches the number equal in value to
(ameliorate&FF&1&)(256) -- if so, set the accumulator back to null.
- Check if the accumulator's number is (-1) by testing the greater-than-zero-ness of one-plus-the-accumulator. After these first two tests the top two extraneous stack values are discarded.
- Decrement the number evaluated to by
"p". The first time, this makes it point to the last (index zero) character in the PTS program, the first character in the Deadfish program. Each time after, it points to the next character in the Deadfish program.
- Check if the character pointed to by
"i"-- if so, increment the accumulator and jump back to index
"2", which is the first
|which will skip past the convert-to-base-A function and restart from the beginning of this list.
- Repeat the above bullet for Deadfish
- Repeat the above bullet for Deadfish
- Repeat the above bullet for Deadfish
"o", but push to the stack the current number of the accumulator,
"walking"run the convert-to-base-A function, pop and output the new top of the stack, and also output a space before jumping back.
- If the character pointed to by
"p"happens to be a "H", this means either the Deadfish code is depleted or contains a capital H for some reason, so the PTS program ends.
- If none of those checks succeeded then we may as well jump back to the start, because why not. The stack is cleared and the program jumps back to
Note that although the base-convert algorithm redefines newline for use in its evaluation, this is limited in scope to only when it is called with
"walking" and does not affect the value of the main program's accumulator.
This program also demonstrates the three ways of storing information in PTS:
- The stack: most reliable, but can be unwieldy at times.
- Three-param pseudo-variable-assignment: can actually be the best (variables are powerful, after all) but because of PTS's quirky treatment of numberical words should be used carefully.
- Modifying the source with
"succeed"and reading it back with two-params: be very careful when doing this, but it is the only way for a PTS program to break a word apart and understand it in any meaningful way beyond the simple PTS builtins.
Info to come
This wiki page is awaiting:
- Summary of stack manipulation
- Summary of flow control
- In-depth discussion of the commands
- Better organisation
- Probably some more category links