Skip to content

Technical overview

tom-writes-code edited this page Mar 18, 2022 · 1 revision

Purpose

rpgle-repl was created with the requirement of being able to execute a small number of lines of RPGLE, and immediately see how they affected the values of local variables. Common early use cases included chaining together built in functions, for example, confirming that %scanrpl('.':'/':%char(%date():*eur)) returned dates in a UK-friendly format.

Previously, this involved writing a short source file, compiling it into a program, and running it in debug mode to evaluate the variables. rpgle-repl does away with these manual steps, turning tedious double-checking tasks into a breeze.

Allowing fixed format RPG to be included is ideal for comparing old and new code where the free format equivalent is non-trivial, such as comparing TESTB and %bitAnd(). The use cases for this have been relatively rare, so I decided against making any attempt to evaluate fixed format code directly.

Being able to easily debug the generated program object is a key requirement. Some variables are too complex for the interpreter to be able to store the results, or sometimes I just need an easy way to call an exported procedure in a service program.

Technical

Detailed descriptions of each procedure within these programs can be found with the prototypes in the reference source.

REPL: From screen to screen

Pressing Run and Compile from repl will start our read-eval-print loop, reading in the user input, evaluating what it means, and printing the results back to the user. Let's take a look at how each program contributes to this process.

repl - the user interface

repl manages all the on-screen user interaction, ensuring that any actions taken are directed to the relevant service program, and that the generated results are returned to the user in a meaningful way.

repl_pseu - storing the user code

repl_pseu is responsible for storing our "pseudo-representation" of the user input to the replsrc table. This also handles changes during on-screen editing such as deleting, splitting or adding lines, reloading the code to the user whenever the screen refreshes, and restoring saved snippets to the current session.

Any results stored to the replrslt table are returned here too, with the code and result lines linked by their line number.

repl_gen - generating the source

repl_gen takes care of generating a source object in QTEMP/QREPL_SRC,REPL_SRC from our pseudo-representation. This will be more than just the user input, as we also call our interpretors to splice in our modifications to the source file, as well as adding boilerplate lines such as the ctl-opt options and setting the *inlr indicator.

repl_ins - inserting data to the source

repl_ins contains our simplest and most boring procedures. It ensures that any lines of code being added to our generated source file are added in the right column depending on whether they represent free or fixed format code.

repl_vars - interpreting variables

repl_vars is invoked by repl_gen every time a free format definition is encountered. Variables, constants and data structures are interpreted, and their definition is stored to the replvars table.

These definitions will later be used by repl_eval when evaluating the results.

repl_eval - evaluating

repl_eval is invoked by repl_gen every time something in free format worth evaluating is encountered. This includes conditional statements (if, else), setting variables (a=b), loops (for, dou, dow), and sql statements.

For anything that can be evaluated, a procedure call to store the results is added to the generated source object, and if relevant, the variable is marked as used.

Once all the user code has been added, repl_eval will be invoked one final time to define each of the result procedures it is intending to call. Each of these will have a definition of the form replResult_<CURRENTPROCEDURE>_<VARIABLENAME>_<ARRAYTYPE>. These will be fairly straightforward procedures - the value of the variable and the current line number will be added to replrslt.

repl_hlpr - helper evaluators

repl_hlpr provides a few quick evaluators for repl_eval to use for common types - such as indicators and loop counters, saving them from being defined every time. This service program will be bound into the generated program.

repl_cmpl - compiling

repl_cmpl takes the existing source object, and attempts to compile it into a module (REPL_MOD) and program (REPL_PGM). If any reference source has been used, this will double check the compile instructions with the user so that additional service programs can be included if required.

repl_usr - performing user actions

repl_usr rounds out our screen to screen journey, running the generated program. The evaluator procedures added by repl.eval will store any results for us, and once the program completes, repl.pseu will put them back onto our screen!

Limitations

The procedure names generated by the evaluator can become quite long, quickly preventing the program from being able to compile.

At some point of writing interpreters able to comprehend reference source, my ability to mentally juggle which program we were in began to slip away, and so increasingly unpredictable behaviour will be observed. See also: Nested data structures.

As more snippets exist, REPLLOAD seems to get exponentially slower, perhaps reflecting that it was a bit of a rushed development. I assume there's a very simple mistake in there somewhere.