RPM language specification, version 1.0 Copyright 1999-2001 Jesse McGrew Distribute freely without editing. 1. Data storage There are four registers: X, Y, Z, and T. They are expressed with brackets: register X Y Z T read ( { [ < write > ] } ) There is also a stack, which can be pushed onto with / and popped from with \ (you get your data BACK with the BACK-slash), but cannot be used as an operand in an expression. Finally, there are named global variables, which can be written and read with the def and rcl commands, respectively. Any of these locations can hold one of three types: - an integer - a string - a proc (code) 2. Program structure Basically, an RPM program is a list of commands. With some exceptions, each command consists of a potentially empty list of registers to read from, a potentially empty list of registers to write to, and a command name: ()foo command named "foo", reading from X and writing to T \>m command named "m", reading from the stack and writing to X The command name is presumed to be all contiguous characters between one register list and the next (except an equal sign or a backtick), so if a command neither reads nor writes any registers, it often must be separated from the command in front of it with an equal sign: \>m=bar "m" reading from the stack and writing to X "bar" neither reading nor writing However, command names that do not begin with letters or numbers are always exactly one character long: '!!!' is a sequence of three commands. 3. Expressions One exception to the command structure shown above is the expression command. This consists of a single write register, a semicolon, an expression, and a final semicolon: >;4D(; evaluate "4D(" and store in X Expressions are evaluated right to left without operator precedence. Each operand can either be a read register, a write register, or a decimal number. A write register operand has the effect of returning the register's current value and storing the value of the expression so far back into the register: ]P( returns X + Y and stores the value of X into Y The binary operators are: Numeric: yPx y added to x ([p]lus) ySx y [s]ubtracted from x yMx x [m]ultiplied by y yDx x [d]ivided by y yAx x bitwise-[A]ND y yOx x bitwise-O[R] y yGx returns either x or y, whichever is [g]reater yLx returns either x or y, whichever is [l]ess Comparative/boolean (0 for false, nonzero for true): yEx x [e]quals y yIx x logical-AND y ([i]ntersection) yUx x logical-OR y ([u]nion) String: yEx x [e]quals y, case-sensitive yIx character y of the string x, or an empty string if invalid ([i]ndex) (the first character is 1) yCx y appended to x ([c]oncatenation) And the unary operators are: Numeric: Cx opposite of x (sign [c]hange) Fx bitwise-NOT x ([f]lip bits) Boolean: Nx logical-[N]OT x String: Lx [l]ength of x Ux [u]ppercase of x Ox l[o]wercase of x Ax [A]SCII value of the first char of x, or 0 if the string is empty Hx c[h]aracter whose ASCII value is x, or empty string if not (0 <= x < 256) 4. Simple commands Commands are described with a register signature such as '2r1w', meaning the command reads two registers and writes one. Some descriptions also use 's' to show that the command reads text from the program stream after the command name. Note that 'register' here can also refer to the stack (the / and \ symbols), and that stack operations are performed in left-to-right order: '//\foo' pops the first input, then pops the second input, then runs foo, then pushes the output. 1r1w = copies a value from one register to another 1w in reads a string from the console 1r out writes a string to the screen 1r1w i2s changes an integer to its string representation 1r1w s2i changes a string to the integer it represents (0 for failure) 1r1w p2s changes a proc to a string containing the commands in the proc 1r1w s2p changes a string to a proc that runs the commands in the string 2r def stores the first register's value into the variable named by a string contained in the second register 1r1w rcl recalls the value of the variable named in the input register into the output register 1r1w type stores the type of a value: 0 = integer, 1 = string, 2 = proc 0r0w ok sets the "ok flag" to true (1) 1r ok returns the value of the ok flag (0 or 1) and sets it to true 5. String literals To create a string value, use the 1ws $ command, which reads a string from the program stream and stores it into a register. The string ends with a backtick, which can be escaped with a backslash. To insert an actual backslash into the string, use two in a row. For example: >$hello world` stores "hello world" in X >$\` backtick, \\ backslash` stores "` backtick, \ backslash" in X 6. The proc command A proc is a value that contains a list of commands, in addition to information about how to execute them. Procs can be created with the 1ws proc command: >proc]in{]s2i` This creates a proc that runs the commands "]in{}s2i" (to read a string from the user, convert it to an integer, and store it in Y) and stores the proc in the X register. Notice that the commands are extracted from the program immediately following the command name, and they are terminated with a backtick (`). Proc and other backtick-terminated commands can be nested, in which case the innermost backtick belongs to the innermost command, and so on. If the first command in the list uses no registers, separate it from proc with an equal sign. To return from a proc before reaching the end, use the 0r0ws ret command with an expression: ret=0E(` returns if X = 0 To run a proc, use the 1r proc command: (proc runs the proc stored in X Or use the def command explained later to assign it a name. 7. The if command The if commands has two forms: if-then (1r1ws) and if-then-else (2r1ws). It reads an expression from the program stream and evaluates it. If the result is nonzero (true), it stores the first input in the output register. Otherwise, it stores the second input with the if-then-else form, or an empty proc with the if-then form. If the expression does not start with a register character, separate it from if with an equal sign. Example: (]type}proc(>s2i`[}if=N0E{`[proc converts the value in X to an integer if it isn't already one Explained: (]type stores the type of X in Y }proc records a proc into Z: (>s2i convert X to an integer and store back into X ` [}if= stores either the proc from Z or a blank proc into Z... N0E{` ... depending on if Y (the type of X) is not 0 (integer) [proc runs the proc stored in Z 8. The while command The only way to create an iterative loop in RPM is with the while command (1r1ws). The while command associates an expression with a proc, so that when the proc is run, it will execute repeatedly as long as the expression evaluates to nonzero (true), or until the ret command is called with a true expression. If the expression is false when the proc is run, it will not execute at all. For example: >;1;]proc(out>;1P(;`{]while=10E(G10`{proc outputs all the numbers from 1 to 10 Explained: >;1; set X = 1 ]proc records a proc into Y: (out output X >;1P(; set X = X + 1 ` {]while tweaks the proc from Y and stores back into Y so it repeats... 10E(G10` ... as long as X <= 10 (the greater of 10 and X is 10) {proc runs the proc stored in Y 9. Recursion Another way to create a loop is to use recursion, which is where a proc calls itself. This is useful in algorithms like factorial: 4 factorial is 4*3*2*1, or 4*(3 factorial). Recursion can be used in RPM with the 0r0w form of the proc command, which calls the currently executing proc, leaving the registers intact, if a condition holds true. For example, this implements the factorial algorithm using recursion: ];1E1G(;}proc>;1;`)proc>;1S(;`[<}if{`[proc(/={/=proc=N{`\]=\)=}proc>;(M1P<;`[}if=N{`[proc Explained: ];1E1G(; sets Y to 1 if X <= 1, or 0 if not }proc records a proc into Z: >;1; set X = 1 ` )proc records a proc into T: >;1S(; set X = X - 1 ` [<}if{` stores Z into Z if Y is true, otherwise stores T into Z [proc runs the proc from Z (/= pushes X onto the stack {/= pushes Y onto the stack proc=N{` recurses if Y is false (leaving the result in X) \]= pops the stack (old value of Y) into Y \)= pops the stack (old value of X) into T }proc records a proc into Z: >;(M1P<; set X = (T + 1) * X ` [}if=N{` stores Z into Z if Y is false, otherwise stores an empty proc [proc runs the proc in Z 10. The def command In addition to storing global variables, the 2r def command allows a proc to be used as if it were a built-in command. If a global variable contains a proc, the proc can be called like a command using the variable's name. It can be called with up to four inputs, which will be passed to the proc in the X, Y, Z, and T registers respectively, and four outputs, which will be read from the proc in the X, Y, Z, and T registers respectively. The registers will be saved before the call is made and restored after it returns, so that the caller of the new command won't see any registers being unintentionally changed. Example: >proc>;2M(;`]$double`({def The proc can now be used as a command called 'double', like this: ];5;{]double{out ... which will display 10. 11. Error checking Commands that encounter errors will set the "ok flag" to false. The value of this flag can be retrieved with the 1w ok command and reset to true with the 0r0w ok command. The "ok flag" will be set to false in these conditions: * an expression attempts to divide by zero * a value of the wrong type is used in a situation where type is important * rcl is called with the name of a variable that does not exist * s2i is called with a string that does not represent an integer Note that the "ok flag" will not be set for syntax errors such as an incorrectly formatted expression or an invalid command name. If a command encounters an error and sets the "ok flag" to false, any results from the command may be invalid.