Post Reply 
RPL stackrobatics decoder
07-09-2018, 04:14 PM (This post was last modified: 07-09-2018 06:06 PM by Claudio L..)
Post: #1
RPL stackrobatics decoder
This thread has the repeated argument of how hard it is to read RPL code.

This brings the question: could it be possible to build an RPL converter to another language, or to pseudocode, or anything a bit more readable?

With a few rules, it should be possible for a simple parser to read RPL and output something more human. This would assist people in the conversion of the vast library or RPL programs to other platforms (Prime?).

The decoder would scan an RPL program and output text. It would define tons of names for functions and local variables (in the example below, I used 'lxxx' for locals, and hope they don't conflict with other variables declared within the program).
The rules could be very simple:
* We need to define a text output for each function/command/token we want to decode
* Any objects put on the stack declare new local variables, and the local variable name gets pushed on a stack instead
* Any command that takes N arguments and leaves M values on the stack, will reassign the N local variables taken as arguments with the results, and declaring new locals if M>N

As an example,I took a piece of code from that same thread(which derives from this post), and "mentally" scanned it with the hypothetical decoder using the rules above:
Code:

 « → n « --> OUTPUT 'function fcn1(n) {'
 0 --> OUTPUT 'l1=0', PUSH 'l1' TO THE STACK
 n --> OUTPUT 'l2=n', PUSH 'l2' to the stack
 1 --> 'l3=1', PUSH 'l3'
 - --> 'l2=l2-l3', PUSH 'l2', DROP l3,l2
WHILE ==> while(
 DUP --> 'l4=l2', PUSH 'l4'
 2 --> 'l5=2', PUSH 'l5'
MOD --> 'l4=l4 MOD l5', DROP l4,l5, PUSH 'l4'
NOT  --> l4= NOT l4' , DROP l4, PUSH 'l4'
REPEAT --> output 'l4', DROP 'l4' ) { (TO CLOSE THE WHILE EXPRESSION)
SWAP
--> HERE STACK CONTAINS 'l2' 'l1'
1 --> l6 = 1
+ --> l1=l1+l6, PUSH 'l1' DROP 'l1',l6
SWAP --> HERE STACK CONTAINS 'l1' 'l2'
2 --> 'l7 = 2'
/ --> 'l2 = l2 / l7', PUSH l2, DROP l7,l2
END --> }
--> HERE THE STACK CONTAINS 'l1' 'l2'
 → s d « --> OUTPUT 's=l1', OUTPUT 'd=l2' OUTPUT '{', DROP l1,l2
 { 2 13 23 1662803 } --> 'l8= { ... }', PUSH l8
IFERR --> OUTPUT try {
1 -->    'l9=1', PUSH l9
4 -->   'l10=4' PUSH l10
FOR i --> OUTPUT 'for i=l9 to l10 {' DROP l9,l10
DUP --> OUTPUT 'l11=l8', PUSH 'l11'
i --> 'l12= i'
GET --> 'l11=GET(l11,l12)', DROP l11,l12, PUSH l11
s --> l13=s, PUSH l13
d --> l14=d, PUSH l14
n --> l15=n, PUSH l15
COMPOSITE? --> OUTPUT 'l11=COMPOSITE?(l11,l13,l14,l15)' (FUNCTION COMPOSITE? MUST'VE BEEN DEFINED BEFORE IN THE DECODER, SOMEHOW), DROP l11,l13,l14,l15, PUSH l11
NOT --> 'l11=NOT l11', DROP l11, PUSH l11
INV --> 'l11=1/l11', DROP l11, PUSH l11
DROP --> OUTPUT discard(l11) , DROP l11
NEXT --> } next
THEN --> } catch {
--> THE DECODER HAS NO WAY OF KNOWING WHERE THE ERROR WOULD OCCUR, SO THIS CANNOT BE FIXED
SWAP DROP --> output as-is
ELSE DROP 1 --> output as-is
END --> OUTPUT }
» --> OUTPUT }
» --> OUTPUT }

After this first scan, we'd get the following output:
Code:

function fcn1(n) {
l1=0
l2=n
l3=1
l2=l2-l3
while( l4=l2; l5=2; l4=l4 mod l5; l4=not l4; l4 ) {
l6=1
l1=l1+l6
l7=2
l2=l2/l7
}
s=l1 d=l2 {
l8={ ... }
try {
l9=1
l10=4
for i=l9 to l10 {
l11=l8
l12=i
l11=GET(l11,l12)
l13=s
l14=d
l15=n
l11=COMPOSITE?(l11,l13,l14,l15)
l11=NOT l11
l11=1/l11
discard(l11)
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}

Is that any better than RPL? Not yet. It needs tons of cleanup. Now we need a parser that does a second pass through that source with a few more rules:
* In a variable definition or an expression: Check if any of the variables in the expression is used in any statements after this one. If not, then replace the variable with its value. If being used later, or if being assigned to in this same statement, just leave it as-is
* In a variable definition: If a variable name being assigned to is never used afterwards (this includes the expression being assigned and all statements after), remove the whole statement, otherwise leave it.
* In a variable definition: If the previous statement assigns to the same variable name, then replace the value from previous statement into the current expression, and remove the previous statement


Let's see what happens to the example above when we apply these rules, pass after pass until there are no more changes:

Code:

FIRST PASS:
function fcn1(n) {
l1=0
l2=n
l3=1
l2=l2-1 (L3 NOT USED AFTERWARDS, REPLACE by value)
while( l4=l2; l5=2; /*l4=l4 mod 2; l5 is replaced, then whole statement is discarded by merge*/ /*l4=not (l4 mod 2); replaced and later DISCARDED BY MERGE*/  not (l4 mod 2) ) { // l4 IS NOT USED AFTERWARDS, SO IT WAS REPLACED BE ITS VALUE
l6=1
l1=l1+1 // l6 replaced
l7=2
l2=l2/2 // l7 replaced
}
s=l1 d=l2 {
l8={ ... }
try {
l9=1
l10=4
for i=1 to 4 {    //(l9,l10 replaced by value)
l11=l8
l12=i
l11=GET(l11,i)    // l12 replaced
l13=s
l14=d
l15=n
/*l11=COMPOSITE?(l11,s,d,n)*/     removed by merge after replacement of l13,l14,l15
l11=NOT COMPOSITE?(l11,s,d,n)        //NOT l11    --> MERGE 
l11=1/(NOT COMPOSITE?(l11,s,d,n)        //1/l11    --> MERGE
discard(l11)
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}








// NEXT PASS

function fcn1(n) {
l1=0
l2=n
// l3 not used, remove whole statement
l2=l2-1
while( l4=l2; not (l2 mod 2) ) { --> l4 not used anymore, replace by value
// l6, l7 defined but not used, removed
l1=l1+1
l2=l2/2
}

s=l1 d=l2 {
l8={ ... }
try {
for i=1 to 4 {
/* l11=l8 */ // removed by merge
// l12 statement removed
/* l11=GET(l8,i) */ // 2 consecutive assignments after the l12 statement was removed, merge the two lines. Later, this line is removed by merging with the next!
// l13,l14,l15 statements removed
l11=1/(NOT COMPOSITE?(GET(l8,i),s,d,n)        // another consecutive assignment to the same variable, replace and merge
discard(l11)
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}

// NEXT PASS

function fcn1(n) {
l1=0
l2=n-1
while( not (l2 mod 2) ) { // l4 statement removed
l1=l1+1
l2=l2/2
}

s=l1 d=l2 {
l8={ ... }
try {
for i=1 to 4 {
l11=1/(NOT COMPOSITE?(GET(l8,i),s,d,n)
discard(1/(NOT COMPOSITE?(GET(l8,i),s,d,n))    // l11 not used anymore, replaced    
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}

// NEXT PASS

function fcn1(n) {
l1=0
l2=n-1
while( not (l2 mod 2) ) { 
l1=l1+1
l2=l2/2
}

s=l1 d=l2 {
l8={ ... }
try {
for i=1 to 4 {
// l11 statement removed, never used    
discard(1/(NOT COMPOSITE?(GET(l8,i),s,d,n)))
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}

//     NO MORE CHANGES NEEDED ON NEXT PASS, DONE

The final result is not too bad:

Code:

function fcn1(n) {
l1=0
l2=n-1
while( not (l2 mod 2) ) { 
l1=l1+1
l2=l2/2
}

s=l1 d=l2 {
l8={ ... }
try {
for i=1 to 4 {
discard(1/(NOT COMPOSITE?(GET(l8,i),s,d,n)))
} next
} catch {
SWAP DROP
ELSE DROP 1
}
}
}

Any more rules or suggestions to improve on this?
Find all posts by this user
Quote this message in a reply
Post Reply 


Messages In This Thread
RPL stackrobatics decoder - Claudio L. - 07-09-2018 04:14 PM
RE: RPL stackrobatics decoder - Claudio L. - 07-09-2018, 06:12 PM
RE: RPL stackrobatics decoder - Claudio L. - 07-10-2018, 04:42 PM
RE: RPL stackrobatics decoder - pier4r - 07-11-2018, 11:24 AM



User(s) browsing this thread: 1 Guest(s)