-
Notifications
You must be signed in to change notification settings - Fork 8
LP Language Engine
The language engine is responsible for executing the AST output from the parser. A major difference from usual esolang interpreters is that Esolang Park language engines don't execute the whole code at once - the engine only exposes functionality to execute a single step of the AST. Everything else including pause-resume, breakpoints and stepping through execution is handled by Esolang Park.
A language engine usually contains the following internal state:
- AST: The parsed form of the code currently being executed.
- Program counter: Keeps track of where the execution currently is in the program.
- Runtime state: This can comprise of several fields that represent execution state at runtime - the Brainfuck stack, values of ingredients in Chef, etc.
The engine has to implement the following four public methods which are used by Esolang Park to to provide esolang-specific functionality:
-
resetState: Trivial - resets all internal state of the engine, including the AST and any stored user input. -
validateCode: Used for live syntax checking - parses code and throws error for bad syntax, without setting any internal state. -
prepare: Called right before starting execution - parse code, store the AST, store the user input if required and prepare all internal state for starting execution. -
executeStep: Execute a single step of the currently loaded AST and return updated runtime state and location of next step to be executed.
Apart from some minor quirks, the engine implementation is entirely up to you. Esolang Park is designed in such a way that a minimum amount of assumptions are made about the execution pattern of the code. You have all the power.
The first three public methods should be fairly obvious to implement - have a look at the Brainfuck or Deadfish language engines to get an idea. This page focuses on the main tasks instead.
You may have noticed the usage of a generic type parameter RS in the class signature of your language engine. It stands for runtime state, and represents the type of value that your language engine sends to your renderer (which we'll implement in the next part of this guide).
RS is defined in the common.ts file (the stub version contains a single numeric field). To decide what fields this type should contain, think of what you want to display to the user in the Visualisation pane. Deadfish only needs to show the current value, so the stub version works for Deadfish. Brainfuck's RS contains fields for the tape contents and the tape pointer. Chef and Shakespeare are more complicated and thus have more fields.
There is one restriction though - the value stored in RS cannot contain something complex like a class instance or a function. Specifically, the value must be supported for structured cloning since it is passed from the web worker to the main thread.
Once you've changed the RS type according to your requirements, we can move forward to implementing the main stuff - the executeStep method.
The only method Esolang Park uses for code execution is the executeStep function. This method is called repeatedly in a loop by the IDE's execution controller, which is responsible for managing pause-resume, execution stepping and breakpoints. On each iteration, the execution of the program progresses a single step. Hence the name, executeStep.
Before proceeding with what the implementation involves, let's have a look at what the executeStep method is supposed to return:
export type StepExecutionResult<RS> = {
rendererState: RS;
output?: string;
nextStepLocation: DocumentRange | null;
codeEdits?: DocumentEdit[];
};Here's a short description of each field:
-
rendererState: This value is passed as props to your ReactRenderercomponent for the Visualisation pane. -
output: If the program emits any output on this step, put the string to append to output in this field. -
nextStepLocation: This contains the location of the section of code that will be executed in the next step. This is used by the editor to highlight the relevant section in yellow. Passingnullindicates the end of the program. -
codeEdits: Only for self-modifying programs. If the source code was edited in this step, put the details in this field. Used to reflect source code changes in the editor.
Note that Esolang Park does not support interactive input yet. The entire user input is taken and passed to the engine at the start of execution.
With that out of the way, we can finally go into the implementation of the executeStep function. The usual implementation contains the following two steps:
-
Process the current instruction: Using the program counter, identify the next instruction to run from the AST, and execute it. Keep track of the output and in case of self-modifying programs, any edits to be made to the code. Also increment or change your program counter as required.
-
Prepare and return the execution result:
- Serialize your updated runtime state to a value of type
RS. - Using the updated program counter, get the location of the instruction to be executed in the next execution step.
- In case of self-modifying esolangs, prepare the code edit as value of type
DocumentEdit. - In addition to the output (if any), pack all of this into a value of type
StepExecutionResult<RS>and return it.
- Serialize your updated runtime state to a value of type
If on any step, the user's code tries to do something invalid - like going to the left of the first cell on a Brainfuck tape - throw a RuntimeError with an appropriate error message and execution will be stopped.
That is really all you need to do. Once you've implemented the four public methods, try a sample program in your esolang to see if the engine is working correctly. If it is, you can move forward to the next and final component - the Renderer component.
Even if your engine is not working correctly - sample programs are giving unexpected output, it may be a good idea to implement a simple version of the Renderer first. That will allow you to use the IDE itself for debugging your language engine by having a constant eye on the internal state of your engine.