CESIL (and CESIL 'Plus') interpreter
Project description
CESIL
A CESIL interpreter written in Python, intended as a "Language Preservation" project.
CESIL is the Computer Education in Schools Instruction Language. It was developed as an introductory instructional language, targeted at British secondary school students (ages 11 to 16). It was developed by International Computers Limited (ICL), a sort of "British version of IBM", as part of the "Computer Education in Schools" (CES) project, and was introduced in 1974.
CESIL & CESIL "Plus"
In addition to the original, standard, CESIL instruction set, this version implements what I call CESIL "Plus". This is my extension of the original CESIL language. It is not "official" nor "standard" in anyway. It consists of seven new instructions (50% of the original total), which incorporate new features, that my CESIL implementation (optionally) adds to the official language.
There are two modes of operation:
-
Standard - Default mode; matches the original implementation of the CESIL language.
-
Plus - Optional; adds instructions for a stack, subroutines, modulo division, random numbers, ASCII character output, integer input from the console and inc/dec operations.
"Plus" mode is enabled by use of the -p or --plus options; the default behavior only observes the original CESIL instructions. In "Plus" mode, the following additional features/instructions are available:
-
A stack capability, and the instructions to support its use:
PUSHandPOP(see "Stack_test.ces"). -
Subroutine support; expands the simple branch/jump capabilities of CESIL with the ability to jump to, or "call" a new point of execution, and then return to the original point of execution (see "Sub_test.ces").
Adds the instructions:
JUMPSR,JSIZERO,JSINEGandRETURN. -
Modulo division; a new
MODULOinstruction that leaves the remainder of a division in the ACCUMULATOR. -
Random number generation; a new
RANDOMinstruction that generations a random number between 0 and its operand and stores it in the ACCUMULATOR. -
Console input; allows user input of an INTEGER via the console and stores it in the ACCUMULATOR, via the
INPUTNinstruction. -
ASCII character outputs; the new
OUTCHARinstruction outputs the ASCII character that corresponds to the value in the ACCUMULATOR. -
INC/DEC instructions; increment (
INC) or decrement (DEC) the value in the ACCUMULATOR by 1.
Installing
For all installation options, the first step is to install Python 3.13.3 or later (may work with earlier versions, provided they have built-in type-hint support, but not tested).
Install via PIP
If you just want to run CESIL or CESIL programs, you can install via pip:
Run:
pip install cesil
Run:
cesil --version
You should see something like:
cesil.py, version 1.0.0
Install via PIPX
You can install CESIL in an isolated environment (recommended) using pipx.
See the installation instructions for pipx, and then:
Run:
pipx install cesil
Run:
cesil --version
You should see something like:
cesil.py, version 1.0.0
Install for Manual/Source Level Usages:
- Clone the repository, or download legacy/cesil.py and requirements.txt
- Run the following command (in the directory you downloaded the above into):
pip install -r requirements.txt
Run:
python3 cesil.py --version
You should see something like:
cesil.py, version 1.0.0
If not, either you don't have Python installed correctly (most likely this is a path issue) or the dependencies (per requirements.txt) did not install.
Usage
Usage is simple:
If you installed via pip or pipx:
cesil filename
If you installed manually or are running source directly:
python3 cesil.py filename
This will run the CESIL program contained in the file "filename". Any output will be displayed on the console.
For full usage details, enter:
cesil --help
or:
python3 cesil.py --help
That will display the complete built-in help, along with options:
Usage: cesil.py [OPTIONS] SOURCE_FILE
CESILPlus - CESIL Interpreter (w/ optional language extentions).
CESIL: Computer Eduction in Schools Instruction Language
"Plus" language extensions add a STACK, SUBROUTINE support, MODULO
division, RANDOM number generation, integer INPUT, ASCII character output,
and INC/DEC functions to the language, enabled with the -p | --plus
options. Extensions are DISABLED by default.
"Plus" Mode - Extension instructions:
MODULO operand - MODULO division of ACCUMULATOR by operand
(sets ACCUMULATOR to REMAINDER)
RANDOM operand - Generates a RANDOM number between 0 and the
value of operand and puts it in the ACCUMULATOR
PUSH - PUSHes the ACCUMULATOR value on to STACK
POP - POPs top value from STACK into the ACCUMULATOR
INC - Increments the ACCUMULATOR by 1
DEC - Decrements the ACCUMULATOR by 1
INPUTN - Accepts an INTEGER from the CONSOLE and places the
value in the ACCUMULATOR
OUTCHAR - Outputs the ACCUMULATOR value as an ASCII character
JUMPSR label - Jumps to SUBROUTINE @ label
JSIZERO label - Jumps to SUBROUTINE @ label if ACCUMULATOR = 0
JSINEG label - Jumps to SUBROUTINE @ label if ACCUMULATOR < 0
RETURN - Returns from SUBROUTINE and continues execution
Options:
-s, --source [t|text|c|card] Text or Card input. [default: text]
-d, --debug [0|1|2|3|4] Debug mode/verbosity level. [default: 0]
-p, --plus Enables "plus" mode language extensions.
--version Show the version and exit.
--help Show this message and exit.
Text vs. Card Mode
Text and Card modes (set via the -s, --source option) determine whether strict adherence to character/column positions from coding sheets or "Cards" are observed, or if simple Text files are expected. The default is Text mode.
Text mode only assumes valid whitespace (spaces or tabs) separation between labels, instructions and operands. The number of spaces/tabs doesn't matter.
In Card mode, there are fixed-width columns for labels and instructions (8 characters each), and then operands can be as wide as the remainder of the line. See card_test.ces for an example.
This means it is possible to have code that works in one mode but not the other. For example:
1234567890123456789012345678901234567890
-------|-------|------------------------
LABEL |INSTRUC|OPERAND
-------|-------|------------------------
SUBTRACT 10
SUBTRACT10
The first line is only valid in Text mode, as the operand will not be read correctly in Card mode. The second line is only valid in Card mode, as there is no whitespace separating the instruction from the operand.
Debug Mode
The debug option (-d, --debug) enables a debug view, which shows the current state of the CESIL execution environment. There are five options for the debug mode, specified as 0 to 4, which break down as follows:
0 = No Debugging (default if no -d | --debug option specified)
1 = Summary output
2 = Summary output w/ step
3 = Verbose output
4 = Verbose output w/ step
Options 1 and 2 show either summary or verbose output, respectively, without pausing execution of the program. Options 3 and 4 show either summary or verbose output, respectively, but pause execution (stops) after every line of the CESIL program until the user hits [Enter], allowing stepping-through the code.
The verbose output, in "stop" mode, looks like this:
DEBUG: [Accumulator: 7] [Flags: None] [Stack Top: 7] -> LINE
[Stack: ] [Variable : Value]
7 -> (Top) COUNT : 7
6
5
4
3
2
1 -> (Bottom)
The top line displays the current value of the ACCUMULATOR, which flags are set (None, Zero, Neg) , the top value on the stack and the current line of code. Below this, the entire contents of the stack are shown, from the top down, along with all current variables and their values.
In summary mode, only the top line of the above display would be shown, but consecutive lines of code display one after the other as they execute:
DEBUG: [Accumulator: 0] [Flags: Zero] [Stack Top: Empty] -> LOAD 0
DEBUG: [Accumulator: 0] [Flags: Zero] [Stack Top: Empty] -> STORE COUNT
DEBUG: [Accumulator: 0] [Flags: Zero] [Stack Top: Empty] -> STACK LOAD COUNT
DEBUG: [Accumulator: 0] [Flags: Zero] [Stack Top: Empty] -> ADD 1
DEBUG: [Accumulator: 1] [Flags: None] [Stack Top: Empty] -> STORE COUNT
DEBUG: [Accumulator: 1] [Flags: None] [Stack Top: Empty] -> PUSH
DEBUG: [Accumulator: 1] [Flags: None] [Stack Top: 1] -> OUT
Why CESIL?
A couple of reasons ...
-
The first is more along the lines of "a lonely impulse of delight".
CESIL was a language I learned in school, in keeping with its CES origins, for my Computer Studies "O-Level". As I was already coding in 6502 and Z80A assembly languages, as well as a BASIC and Logo, it was a bit of a curiosity - but also a necessity for the classwork. The very limited nature of the language made me think about problems in a different way, which was academically interesting as well as oddly enjoyable.
I suppose nostalgia, with its propensity to make things seem much more fun/interesting than the reality of the time, is also partly at work here. There are days I miss the bare-metal programming I did back in the days of the Altair 8800, NasCom 1, Atari 400/800 and Sinclair Research ZX81 and ZX Spectrum.
I looked at some of the other open-source CESIL implementations, and they were fun to play with for a little bit. But it was really creating a CESIL interpreter (originally it was "compiled") that was fueling my interest.
-
The second is a bit more "practical" ... or as practical as things are likely to get when it comes to "dead" languages.
As part of my on-going computer science/engineering/programming mentoring efforts, I wanted to introduce my mentees to a very simple, limited, programming model and get away from massively powerful functions riding on top of multiple levels of abstraction. Have them write some code that way, compare and contrast it with modern, higher-level, approaches.
And then, just as importantly, have them implement a similar language themselves.
From there, I wanted something to use in additional mentoring to look at ways of making code maintainable and both easily and cleanly extensible - which is what drove this "final" version (vs. earlier prototypes).
-
And third, to use the extension mechanisms from point two, add additional features/instructions/capabilities (e.g., a stack, modulo division and subroutines) and see what affect those would have on the way code is written to solve different problems.
Implementation Approach & Theory of Operation
There are two current versions of the CESIL interpreter in this repository, both of which have the same fundamental implementation, approach and theory of operation - they differ only in how they are organized from a file/project perspective:
-
A single, "legacy", all-in-one, script that can be run as-is without installing anything other than it's dependencies, using standard Python and PIP tooling (as per the instructions under the "Installing" section.
-
The "current" version, which can be built and run using uv and/or built with uv and installed with pipx (and which I intend to post to PYPI).
The real/primary implementation, in single-file form, is legacy/cesil.py. This is an evolution of earlier, experimental, "prototype" versions that makes major changes to the internal implementation of CESIL instructions, how they are enumerated and, most importantly, how they are executed.
In common with the prototypes, particularly prototypes/CESILPy.py, is the parsing logic, command line interface, general structure and some of the "extension" instructions. However, here the extensibility of the supported instruction set is made extremely easy, and the execution process is more generalized to allow simple, and flexible, composition, of new instructions and/or the addition of new language features:
Defining CESIL Instructions
To implement an instruction for our CESIL interpreter, we provide a simple Python method, as a "protected" member of the CESIL class, which can then manipulate the state of that class to provide its behavior. The example below implements the "HALT" instruction:
def _halt(self):
'''Halts program execution'''
self._halt_execution = True
In this case, it sets the program execution flag _halt_exection, which causes the run() loop to terminate the program.
To identify a method as a CESIL instruction, rather than some other method in the class, we apply a custom decorator to the method's definition:
@instruction("HALT", OpType.NONE, False)
def _halt(self):
'''Halts program execution'''
self._halt_execution = True
The @instruction decorator provides the CESIL instruction "mnemonic" that will be mapped to the Python function name (to prevent collisions between Python and CESIL keywords/instruction names), indicates the type of operand the instruction uses, and whether the instruction is considered part of my "Plus", or extended, version the language.
At runtime, all of the CESIL instructions are "registered" - i.e., evaluated and added to an instructions dictionary, indexed by CESIL instruction "mnemonic", and containing a function pointer to the implementing Python function, as well as the operand type the function uses.
Adding a new "Extension" Instruction
This is as simple as writing a simple Python function, within the CESIL class, and then applying the @instruction decorator. As an example, the following code adds the MODULO instruction, which performs modulo-division on the CESIL accumulator:
@instruction("MODULO", OpType.LITERAL_VAR, True)
def _modulo(self):
'''Modulo division of ACCUMULATOR by OPERAND; ACCUMULATOR = REMAINDER'''
self._accumulator %= self._get_real_value(self._current_line.operand)
The _modulo() function simply assigns the modulo-divided value of the accumulator back to the accumulator (i.e., puts the remainder on the accumulator). Note that the @instruction decorator indicates this function can operate on LITERAL or VARIABLE operands, and that it is an "extension" or "plus" instruction.
No other code needs to be touched to make this work.
ALL of the supported CESIL instructions here are implemented in this fashion.
Instructions that change State and/or Program Flow
Most instructions change the internal state of the CESIL execution environment, the entirety of which is accessible to any CESIL instruction. However, most commonly mutated states are:
-
_accumulator - The current
accumulatorvalue.Typically arithmetic operations, as well as a the
LOADfunction, alter theaccumulator. -
_instruction_ptr - Index of the next instruction to be executed.
Branching instructions, such as
JUMP,JIZERO,JSINEGorRETURNchange_instruction_ptr, to transfer control/execution to a different part of the CESIL program.Note: When altering
_instruction_ptrwithin a CESIL instruction, the_branchflag should be set toTrue; this prevents therun()loop from simply executing the next instruction it was going to and causes it to transfer execution to the now-current value of_instruction_ptr. -
_data_ptr - Index of the current data value.
-
_stack - The internal stack (as a Python list); available in "Plus" mode.
Additionally there are two flags that control the flow of program execution, and these should be set/unset within the Python implementation of a CESIL instruction according to what that instruction is intended to do:
-
_halt - If set (
True), the CESIL program will terminate upon completion of the current instruction. -
_branch - if set (
True), then_instruction_ptrwill NOT be incremented, and control will transfer to the current line of code indicated by_instruction_ptrwhen the current CESIL instruction completes.
Running the CESIL Program (Executing Instructions)
The run() loop is relatively simple. It steps through the items in the CESIL program, one CodeLine at a time, and invokes the function provided by the function pointer in the _instructions dictionary, using CodeLine.instruction as the lookup key:
-
The invoked function performs its operations on the state of the CESIL environment, then control returns to the
run()loop. -
The value of the
accumulatoris checked for overflow; and an exception raised if necessary. -
The
_haltflag is checked, and if set the program is terminated (exits therun()loop). -
The
_branch flagis checked; if unset (False) then_instruction_ptris incremented - and if set (True) it is NOT incremented (to observe the behavior of a branching instruction and what it has done to the_instruction_ptr). -
The
run()loop continues with whatever line of code is now indicated by the_instruction_ptr.
Prototypes
The prototypes/ folder contains the source code for my earlier, experimental, implementations of CESIL in Python:
-
More of an exploratory implementation, which doesn't follow any particular Python coding convention or standard - so, for example, long lines are common (and artificially shorten the apparent SLOC for the implementation). There's no command-line; so running code requires changing the source to point to the file to be executed. It is largely monolithic, with just one class for the CESIL interpreter and another to represent a line of code. It's not particularly "Pythonic" (doesn't necessarily do things in a way that it is idiomatic to general Python code). Really just a quick-and-dirty test-bed for the approach and some logic.
-
This adds a proper command line, allowing CESIL code to be run from the console, optional "extension" instructions, and a simple "debugger" - which lets yo see the overall state of the CESIL execution environment, including variable values and STACK contents, as code runs.
Multiple classes are now used to represent lines of code (
CodeLine), the overall CESIL Program (Program) - including code, variables, labels and data, as well as the execution state of the CESIL environment (State).Line lengths are now limited, and an overall more "Pythonic" approach is taken to the implementation.
-
This is largely a restructuring of the above CESILPlus.py implementation. It moves the
ProgramandStatedata back into the main CESIL class, since they were just groups of variables anyway. Other features and functions remain the same.Some adjustments to properly observe the PEP8 conventions were made, some functions/methods were simplified (or made "less clever") or, again, my more "Pythonic".
Prototype Operation
While the features, and details, of these three versions vary, as do the coding standards/style they observe, they all use a similar approach to executing CESIL code. All of them read the code from a file, then validate and parse it into an ordered list of CodeLine instances. Each CodeLine represents one line of CESIL Code, which can include a label and instruction and an operand (not all are present for all lines of code).
Executing CESIL Code
Executing the code, in the prototypes, involves moving an "instruction pointer" through the CodeLine list, one line at a time, and uses a large if/elif/else block to identify each CESIL instruction and executes the necessary Python code to implement that instruction's behavior (e.g., arithmetic, looping, conditional branching etc.)
This is a "simple" or, at least, easily readable, way to implement the execution process (the run() method). For the 14 instructions that comprise the CESIL language, it's reasonably manageable - taking a total of 45 lines. However, the overall structure isn't very extensible; the run() method would rapidly become unwieldy if you added additional "extension" instructions.
Notice to Copyright Holders
Attempts to identify and/or contact current copyright holders for assets such as the "CESIL Reference Card" and the CESIL "Coding Sheet" (reproduced from scratch as a visual/content facsimilie of the original - including original copyright notice) were unsuccessful.
I believe my usage/recreation of these assets falls under fair-use, for non-profit educational and/or documentary/editorial purposes.
However, if said copyright holders have any issue with their reproduction here, I will be happy to remove these assets, modify their presentation, and/or transfer ownership (including the CESIL.org domain), at their behest.
No legal/formal request/demand needed, just let me know.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cesil-1.0.0.tar.gz.
File metadata
- Download URL: cesil-1.0.0.tar.gz
- Upload date:
- Size: 168.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81f38b5ecf49e16b15e1134662981772f41b6277d56f5ea777586fa662beeef8
|
|
| MD5 |
9644778e97da48e971097b87efbd774f
|
|
| BLAKE2b-256 |
89432752767bf061e6407cc2b4c7dcc3054dbfd9173b24251d763d01d42cda36
|
File details
Details for the file cesil-1.0.0-py3-none-any.whl.
File metadata
- Download URL: cesil-1.0.0-py3-none-any.whl
- Upload date:
- Size: 29.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ab13e143f553f2682d884e60421bec10ed7371fd9126fe46e9b87a2160a5dda
|
|
| MD5 |
f70c5b10f93e42f01a571d1b0e60849a
|
|
| BLAKE2b-256 |
847f51c0b4f93d652c3228a1331f101c82ff1529bd4c4dd7c52bdc360595455e
|