psei is a lightweight interpreter for Cambridge International AS & A Level Computer Science 9618-style pseudocode.
It implements a practical subset of Cambridge-style pseudocode and can be used for:
- running
.pseudofiles locally - experimenting with pseudocode in a REPL
- executing pseudocode from Python tests or applications
- building teaching examples
- checking common runtime and type errors
- checking source against the Cambridge 2027-2029 pseudocode guide
pseiis not an official Cambridge tool. Its compliance profile checks plain-text source rules but cannot validate presentation details such as font choice. Its goal is to provide a useful, testable interpreter and checker for a Cambridge-style pseudocode subset.
Install as a CLI tool:
pipx install pseiOr install with pip:
python -m pip install pseiCreate hello.pseudo:
OUTPUT "Hello"
Run it:
pseudo run hello.pseudo- Features
- Installation
- Command-line usage
- REPL usage
- Python API usage
- Pseudocode examples
- Strict mode
- Static semantic analysis
- Cambridge 2027 compliance checking
- Cambridge conformance matrix
- Resource limits
- File handling
- User-defined types
- Procedures and functions
- Object-oriented pseudocode
- Built-in functions
- Errors
- Development
- Project structure
- Current limitations
Supported:
DECLARECONSTANT- assignment using
← INPUTOUTPUT- comments using
//
Supported data types:
INTEGERREALCHARSTRINGBOOLEANDATE
Arithmetic operators:
+-*/DIVMOD
Comparison operators:
=<><<=>>=
Logic operators:
ANDORNOT
String concatenation:
&
AND and OR use short-circuit evaluation.
Supported control structures:
IF ... THEN ... ELSE ... ENDIFCASE OF ... OTHERWISE ... ENDCASEFOR ... TO ... STEP ... NEXTWHILE ... ENDWHILEREPEAT ... UNTIL
Supported:
- one-dimensional arrays
- two-dimensional arrays
- explicit lower and upper bounds
- bounds checking
- whole-array assignment with copy semantics
Supported:
- enumerated types
- pointer types
- set types
- record types
- class/object types
Supported:
PROCEDUREFUNCTIONCALLRETURNBYVALBYREF
Supported text file operations:
OPENFILE ... FOR READOPENFILE ... FOR WRITEOPENFILE ... FOR APPENDREADFILEWRITEFILECLOSEFILEEOF(...)
Supported random file operations:
OPENFILE ... FOR RANDOMSEEKGETRECORDPUTRECORD
Supported:
CLASS ... ENDCLASSPUBLICPRIVATEINHERITSSUPER- constructors using
PROCEDURE NEW(...) - object creation using
NEW ClassName(...) - method calls using
Object.Method(...)
git clone <repo-url>
cd pseiInstall uv, then create and synchronize the project environment from the committed lock file:
uv sync --lockeduv creates .venv automatically and installs the project plus the default
dev dependency group. There is no need to activate the environment; prefix
project commands with uv run.
Python requirement:
Python 3.10 through 3.14
The package provides two equivalent console commands:
pseudo
pseipseudo run path/to/program.pseudoExample:
pseudo run examples/passing/declare_assign_output.pseudoYou can also run it as a Python module:
python -m psei run examples/passing/declare_assign_output.pseudopseudo run path/to/program.pseudo --strictExample:
pseudo run examples/errors/strict_ascii_assignment.pseudo --strictRun the static semantic analyzer on a file:
pseudo analyze path/to/program.pseudoUse --strict to require declarations before assignment, or
--recommendations to report reads before explicit initialization:
pseudo analyze path/to/program.pseudo --strict --recommendationsMachine-readable output is available for editors and CI:
pseudo analyze path/to/program.pseudo --format jsonCheck syntax and presentation without executing the program:
pseudo check path/to/program.pseudoThe default profile covers the Cambridge pseudocode guide for examinations in 2027, 2028 and 2029:
pseudo check path/to/program.pseudo --profile cambridge-2027Use JSON output for editors, CI or other tools:
pseudo check path/to/program.pseudo --format jsonExamination line numbers are detected automatically. Override detection when needed:
pseudo check path/to/program.pseudo --line-numbers present
pseudo check path/to/program.pseudo --line-numbers absentIf a program produces a lexical, parse or runtime error:
- the error message is written to
stderr - the process exits with status code
1
pseudo check exits with status code 1 when it reports any error or warning.
A compliant file exits with status code 0.
pseudo analyze exits with status code 1 for semantic errors. Warnings are
reported but keep the exit status at 0.
Start the REPL:
pseudo replOr:
python -m psei replStart the REPL in strict mode:
pseudo repl --strictAvailable REPL commands:
:help show help
:vars show variables in the current runtime
:reset reset the runtime
:quit exit
:exit exit
Example session:
pseudo> DECLARE X : INTEGER
pseudo> X ← 10
pseudo> OUTPUT X + 5
15
pseudo> :vars
X : INTEGER = 10
pseudo> :quit
For multi-line constructs such as IF, WHILE, PROCEDURE, FUNCTION and CLASS, the REPL waits until the block is complete.
psei can also be used as a Python library.
from psei import run_source
source = """
DECLARE Counter : INTEGER
Counter ← 0
Counter ← Counter + 1
OUTPUT Counter
"""
run_source(source)Output:
1
By default, OUTPUT uses Python's print. To capture output in tests, create a custom Runtime.
from psei import Runtime, run_source
output = []
runtime = Runtime(output_writer=output.append)
run_source("""
OUTPUT "Hello"
OUTPUT "World"
""", runtime)
assert output == ["Hello", "World"]from psei import Runtime, run_source
inputs = iter(["41"])
output = []
runtime = Runtime(
input_provider=lambda: next(inputs),
output_writer=output.append,
)
run_source("""
DECLARE X : INTEGER
INPUT X
OUTPUT X + 1
""", runtime)
assert output == ["42"]from psei import run_file
run_file("path/to/program.pseudo")run_file() uses a local file system rooted at the directory containing the pseudocode file.
from psei import analyze_file, analyze_source
report = analyze_source("""
DECLARE Count : INTEGER
Count ← "one"
""")
assert not report.valid
assert report.diagnostics[0].code == "SEM004"
file_report = analyze_file(
"path/to/program.pseudo",
strict=True,
recommendations=True,
)analyze_program() is also available when an application already has a parsed
Program AST. Reports and diagnostics provide to_dict() for JSON-oriented
integrations.
DECLARE Counter : INTEGER
Counter ← 0
Counter ← Counter + 1
OUTPUT Counter
Output:
1
DECLARE Values : ARRAY[1:4] OF INTEGER
DECLARE I : INTEGER
DECLARE Total : INTEGER
Total ← 0
FOR I ← 1 TO 4
Values[I] ← I * 2
Total ← Total + Values[I]
NEXT I
OUTPUT "Total=", Total
Output:
Total=20
DECLARE Score : INTEGER
Score ← 75
IF Score >= 50 THEN
OUTPUT "Pass"
ELSE
OUTPUT "Fail"
ENDIF
Output:
Pass
DECLARE Mark : INTEGER
Mark ← 75
CASE OF Mark
0 TO 49 : OUTPUT "Fail"
50 TO 69 : OUTPUT "Pass"
70 TO 100 : OUTPUT "Distinction"
OTHERWISE : OUTPUT "Invalid"
ENDCASE
Output:
Distinction
DECLARE Number : INTEGER
Number ← 27
WHILE Number > 9
Number ← Number - 9
ENDWHILE
OUTPUT Number
Output:
9
DECLARE Number : INTEGER
Number ← 0
REPEAT
Number ← Number + 1
UNTIL Number = 3
OUTPUT Number
Output:
3
Strict mode is a runtime guardrail. Use pseudo check for Cambridge style and
exam-format validation without executing the program.
Enable strict mode from the command line:
pseudo run program.pseudo --strictEnable strict mode from Python:
from psei import Runtime, run_source
runtime = Runtime(strict=True)
run_source("""
DECLARE X : INTEGER
X ← 1
""", runtime, strict=True)Strict mode currently enforces:
- assignment must use
← - ASCII assignment
<-is rejected - variables must be declared before assignment
- identifiers may contain only ASCII letters, digits and
_ - identifiers must start with an ASCII letter
Non-strict mode currently allows:
- assignment using either
←or<- - assignment to undeclared variables, creating them with inferred types
- non-ASCII alphabetic characters in identifiers
Both modes still perform core runtime checks, including:
- assignment type checks
- constant immutability
- array bounds checks
- unknown type checks
- record field checks
- enumerated type checks
- file mode checks
- division by zero checks
- Boolean condition checks
- procedure/function arity checks
- function return type checks
BYREFlvalue and type checks
The static analyzer parses and checks a complete program without executing statements, requesting input or opening pseudocode files. It resolves case-insensitive symbols and user-defined types before checking:
- declarations, duplicate names and unknown identifiers or types
- assignment, condition, operator and return-value types
- array indices, record fields, object properties and pointer dereferences
- procedure, function, method and constructor usage
- argument count,
BYVALcompatibility and writableBYREFlvalues - class inheritance, duplicate or private members and constructor form
- whether every function control-flow path returns
- statements that follow an unconditional return
Normal analysis mirrors non-strict execution by allowing an assignment to
introduce an inferred variable. strict=True rejects that allowance.
recommendations=True additionally emits SEM003 when a declared variable is
read before an explicit assignment; this is advisory and is not a
flow-sensitive definite-assignment proof.
Semantic diagnostics use these stable codes:
| Code | Severity | Meaning |
|---|---|---|
SEM001 |
error | Undefined identifier |
SEM002 |
error | Duplicate declaration |
SEM003 |
warning | Read before explicit initialization |
SEM004 |
error | Incompatible assignment or constant mutation |
SEM005 |
error | Invalid condition or operator operand |
SEM006 |
error | Unknown or incorrectly used callable |
SEM007 |
error | Wrong argument count |
SEM008 |
error | Incompatible BYVAL argument |
SEM009 |
error | Invalid BYREF argument or address-of operand |
SEM010 |
error | Invalid member, index or pointer access |
SEM011 |
error | Function may finish without returning |
SEM012 |
error | RETURN outside a function |
SEM013 |
error | Incompatible return value |
SEM014 |
warning | Unreachable statement |
SEM015 |
error | Unknown or invalid type relationship |
Warnings do not make a SemanticReport invalid. The analyzer remains
conservative about value-dependent behavior: array bounds, division by zero,
file state, pointer initialization and similar facts are still checked at
runtime.
The cambridge-2027 profile checks source code against the Cambridge
International AS & A Level Computer Science 9618 pseudocode guide for
examinations in 2027, 2028 and 2029.
The detailed
Cambridge 2027-2029 conformance matrix
marks each behavior as formal syntax, a recommendation, a compatibility
allowance or a documented psei extension.
It performs static checking only. It parses the source but never executes it, reads pseudocode input or opens pseudocode files.
The profile currently checks:
- upper-case Cambridge keywords and standard function names
- three-space structural indentation and tab usage
- the Cambridge
←assignment operator - ASCII identifier characters
- consistent case-insensitive identifier spelling
- lexer and parser compatibility with the formal guide syntax
- examination line numbers, including optional preprocessing before parsing
- uses of documented
pseioperations that are not defined by the guide - normal-mode allowances for blank
OUTPUT, expressionCASEselectors and arrays with more than two dimensions - the guide's page 19
CALL Beepinconsistency against the formalCALL Beep()grammar in section 8.1 - semantic diagnostics for names, types, calls, members and return paths
Diagnostics have stable codes, error or warning severity, and one of four
categories:
| Category | Meaning |
|---|---|
formal |
An explicit Cambridge syntax or presentation rule |
recommendation |
Guidance described as usual or good practice |
compatibility |
Syntax accepted for normal psei compatibility but not the formal Cambridge format |
extension |
A documented psei feature outside the guide |
| Code | Category | Meaning |
|---|---|---|
C2027-A001 |
compatibility |
Non-Cambridge assignment operator |
C2027-C001 |
compatibility |
Procedure call missing formal parentheses |
C2027-I001 |
recommendation |
Tab used for indentation |
C2027-I002 |
recommendation |
Structural indentation differs from three-space nesting |
C2027-ID001 |
formal |
Inconsistent case-insensitive identifier spelling |
C2027-ID002 |
formal |
Non-ASCII identifier character |
C2027-K001 |
formal |
Keyword or standard function name is not upper-case |
C2027-L001 |
formal |
Other lexical error |
C2027-N001 |
formal |
Line numbers do not increase |
C2027-P001 |
formal |
Parser error against the formal syntax |
C2027-X001 |
extension |
Documented psei operation outside the guide |
C2027-X002 |
compatibility |
OUTPUT has no value |
C2027-X003 |
compatibility |
CASE OF uses an expression instead of an identifier |
C2027-X004 |
compatibility |
Array declaration has more than two dimensions |
Warnings are compliance failures but do not imply that normal, non-strict
execution would fail. For example, normal execution accepts lower-case
keywords while the compliance profile reports them. Text diagnostics include
the category in square brackets, and JSON diagnostics expose it as the
category field.
Use the checker from Python:
from psei import check_file, check_source
report = check_source("""
DECLARE Count : INTEGER
Count ← 1
OUTPUT Count
""")
assert report.compliant
file_report = check_file("program.pseudo")
for diagnostic in file_report.diagnostics:
print(diagnostic.format("program.pseudo"))line_numbers can be set to "auto", "present" or "absent" in the
Python API. The default is "auto". Semantic findings retain their SEM###
codes and use the formal compliance category.
Runtime applies conservative execution limits by default to protect the interpreter from runaway programs.
Default limits:
Runtime(
max_steps=1_000_000,
max_array_elements=1_000_000,
max_call_depth=1_000,
max_output_chars=1_000_000,
)| Option | Purpose |
|---|---|
max_steps |
Limits executed statements and loop iterations |
max_array_elements |
Limits the number of elements in a single array |
max_call_depth |
Limits procedure, function and method call depth |
max_output_chars |
Limits the total number of output characters |
Example:
from psei import Runtime, run_source
from psei.errors import PseudoRuntimeError
runtime = Runtime(max_steps=1000)
try:
run_source("""
WHILE TRUE
ENDWHILE
""", runtime)
except PseudoRuntimeError as error:
print(error)To disable a specific limit, pass None:
runtime = Runtime(max_steps=None)These limits are not a full security sandbox. If you run untrusted code in production, also use process-level timeouts, memory limits, containers or operating-system sandboxing.
run_source() uses an in-memory file system by default.
This means:
- no real files are created
- execution is deterministic
- tests and REPL usage are easier to manage
Example:
DECLARE Line : STRING
OPENFILE "Log.txt" FOR WRITE
WRITEFILE "Log.txt", "Hello"
CLOSEFILE "Log.txt"
OPENFILE "Log.txt" FOR READ
READFILE "Log.txt", Line
OUTPUT Line
CLOSEFILE "Log.txt"
run_file() uses a local file system.
Important behavior:
- relative paths are resolved beside the pseudocode source file
- absolute paths are rejected
- paths escaping the program directory are rejected
- text files are read and written as UTF-8
- random files are persisted as JSON
DECLARE LineOfText : STRING
OPENFILE "FileA.txt" FOR WRITE
WRITEFILE "FileA.txt", "First"
WRITEFILE "FileA.txt", "Second"
CLOSEFILE "FileA.txt"
OPENFILE "FileA.txt" FOR READ
WHILE NOT EOF("FileA.txt")
READFILE "FileA.txt", LineOfText
OUTPUT LineOfText
ENDWHILE
CLOSEFILE "FileA.txt"
Output:
First
Second
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil : StudentRecord
DECLARE Loaded : StudentRecord
Pupil.LastName ← "Johnson"
Pupil.YearGroup ← 6
OPENFILE "StudentFile.Dat" FOR RANDOM
SEEK "StudentFile.Dat", 10
PUTRECORD "StudentFile.Dat", Pupil
SEEK "StudentFile.Dat", 10
GETRECORD "StudentFile.Dat", Loaded
CLOSEFILE "StudentFile.Dat"
OUTPUT Loaded.LastName, ":", Loaded.YearGroup
Output:
Johnson:6
Random files can store:
- scalar values
- arrays
- records
- sets
Random files cannot store:
- object instances
- pointer values
Local random files use the deterministic, versioned psei-random-v1 JSON
format. Persisted addresses, types, record fields, array elements and scalar
values are validated when a file is opened; malformed data raises
PseudoRuntimeError. Non-finite REAL values cannot be persisted.
TYPE Season = (Spring, Summer, Autumn, Winter)
DECLARE ThisSeason : Season
ThisSeason ← Summer
OUTPUT ThisSeason
Output:
Summer
Enumerated values are case-insensitive.
If a variable has the same name as an enumerated value, the variable shadows the enumerated value.
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE FirstName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil : StudentRecord
Pupil.LastName ← "Johnson"
Pupil.FirstName ← "Leroy"
Pupil.YearGroup ← 6
OUTPUT Pupil.LastName, ",", Pupil.FirstName, ",", Pupil.YearGroup
Output:
Johnson,Leroy,6
Record assignment uses copy semantics. Assigning one record to another does not alias their fields.
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil1 : StudentRecord
DECLARE Pupil2 : StudentRecord
Pupil1.LastName ← "Johnson"
Pupil1.YearGroup ← 6
Pupil2 ← Pupil1
Pupil1.YearGroup ← 7
OUTPUT Pupil2.YearGroup
OUTPUT Pupil1.YearGroup
Output:
6
7
TYPE StudentRecord
DECLARE Name : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Form : ARRAY[1:2] OF StudentRecord
Form[1].Name ← "Ali"
Form[1].YearGroup ← 12
Form[2].Name ← "Mei"
Form[2].YearGroup ← 11
OUTPUT Form[1].Name, ":", Form[1].YearGroup
OUTPUT Form[2].Name, ":", Form[2].YearGroup
Output:
Ali:12
Mei:11
TYPE TIntPointer = ^INTEGER
DECLARE X : INTEGER
DECLARE P : TIntPointer
X ← 10
P ← ^X
OUTPUT P^
P^ ← 20
OUTPUT X
Output:
10
20
TYPE LetterSet = SET OF CHAR
DEFINE Vowels ('A','E','I','O','U') : LetterSet
OUTPUT Vowels
Output:
{A, E, I, O, U}
DEFINE creates a constant set. Use DECLARE when the set needs to be
changed:
DECLARE Selected : LetterSet
Selected ← Vowels
CALL SETADD(Selected, 'Y')
CALL SETREMOVE(Selected, 'A')
Set assignment uses copy semantics. The operations below return a new set and do not change either operand:
Combined ← UNION(SetA, SetB)
Shared ← INTERSECTION(SetA, SetB)
OnlyA ← DIFFERENCE(SetA, SetB)
EitherButNotBoth ← SYMMETRICDIFFERENCE(SetA, SetB)
Set query functions:
| Function | Result |
|---|---|
CONTAINS(SetValue, Element) |
Whether the element belongs to the set |
CARDINALITY(SetValue) |
Number of distinct elements |
ISEMPTY(SetValue) |
Whether the set is empty |
ISSUBSET(SetA, SetB) |
Whether every element of SetA is in SetB |
ISPROPERSUBSET(SetA, SetB) |
Whether SetA is a strict subset of SetB |
ISSUPERSET(SetA, SetB) |
Whether SetA contains every element of SetB |
ISPROPERSUPERSET(SetA, SetB) |
Whether SetA is a strict superset of SetB |
ISDISJOINT(SetA, SetB) |
Whether the sets have no elements in common |
Set mutation procedures:
| Procedure | Effect |
|---|---|
CALL SETADD(SetValue, Element) |
Adds an element; existing elements are unchanged |
CALL SETREMOVE(SetValue, Element) |
Removes an element, or raises an error if absent |
CALL SETDISCARD(SetValue, Element) |
Removes an element if present |
CALL SETCLEAR(SetValue) |
Removes all elements |
The Cambridge pseudocode guide does not define standard notation for these
operations. These named functions and procedures are a documented psei
extension.
PROCEDURE Hello()
OUTPUT "Hello"
ENDPROCEDURE
CALL Hello()
Output:
Hello
PROCEDURE Square(Size : INTEGER)
FOR Side ← 1 TO 4
OUTPUT "Side length=", Size
NEXT Side
ENDPROCEDURE
CALL Square(100)
Parameters are passed by value by default.
PROCEDURE AddOne(X : INTEGER)
X ← X + 1
ENDPROCEDURE
DECLARE A : INTEGER
A ← 5
CALL AddOne(A)
OUTPUT A
Output:
5
BYREF parameters modify the caller's variable, array element, record field, object property or pointer dereference.
PROCEDURE AddOne(BYREF X : INTEGER)
X ← X + 1
ENDPROCEDURE
DECLARE A : INTEGER
A ← 5
CALL AddOne(A)
OUTPUT A
Output:
6
The current passing mode continues across comma-separated parameters until another BYVAL or BYREF keyword appears.
PROCEDURE Swap(BYREF X : INTEGER, Y : INTEGER)
DECLARE Temp : INTEGER
Temp ← X
X ← Y
Y ← Temp
ENDPROCEDURE
In the example above, both X and Y are passed by reference.
To reset the mode explicitly:
PROCEDURE Test(BYREF X : INTEGER, BYVAL Y : INTEGER)
X ← 10
Y ← 20
ENDPROCEDURE
FUNCTION Max(Number1 : INTEGER, Number2 : INTEGER) RETURNS INTEGER
IF Number1 > Number2 THEN
RETURN Number1
ELSE
RETURN Number2
ENDIF
ENDFUNCTION
OUTPUT "Maximum=", Max(10, 20)
Output:
Maximum=20
Function calls must be used as part of an expression.
Valid:
OUTPUT Max(10, 20)
X ← Max(10, 20)
Invalid:
Max(10, 20)
Function parameters cannot be passed BYREF.
CLASS Player
PRIVATE Attempts : INTEGER
Attempts ← 3
PUBLIC PROCEDURE SetAttempts(Number : INTEGER)
Attempts ← Number
ENDPROCEDURE
PUBLIC FUNCTION GetAttempts() RETURNS INTEGER
RETURN Attempts
ENDFUNCTION
ENDCLASS
DECLARE P : Player
P ← NEW Player()
OUTPUT P.GetAttempts()
P.SetAttempts(5)
OUTPUT P.GetAttempts()
Output:
3
5
Constructors are procedures named NEW.
CLASS Pet
PRIVATE Name : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING)
Name ← GivenName
ENDPROCEDURE
PUBLIC FUNCTION GetName() RETURNS STRING
RETURN Name
ENDFUNCTION
ENDCLASS
MyPet ← NEW Pet("Kitty")
OUTPUT MyPet.GetName()
Output:
Kitty
CLASS Pet
PRIVATE Name : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING)
Name ← GivenName
ENDPROCEDURE
PUBLIC FUNCTION GetName() RETURNS STRING
RETURN Name
ENDFUNCTION
ENDCLASS
CLASS Cat INHERITS Pet
PRIVATE Breed : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING, GivenBreed : STRING)
SUPER.NEW(GivenName)
Breed ← GivenBreed
ENDPROCEDURE
PUBLIC FUNCTION GetBreed() RETURNS STRING
RETURN Breed
ENDFUNCTION
ENDCLASS
MyCat ← NEW Cat("Kitty", "Shorthaired")
OUTPUT MyCat.GetName()
OUTPUT MyCat.GetBreed()
Output:
Kitty
Shorthaired
PUBLIC members can be accessed from outside the object.
PRIVATE members can only be accessed from methods or initializers of the class that declares them.
Example:
CLASS Account
PRIVATE Balance : INTEGER
PUBLIC PROCEDURE NEW(StartBalance : INTEGER)
Balance ← StartBalance
ENDPROCEDURE
PUBLIC FUNCTION GetBalance() RETURNS INTEGER
RETURN Balance
ENDFUNCTION
ENDCLASS
A ← NEW Account(100)
OUTPUT A.GetBalance()
Output:
100
This external access raises a runtime error:
OUTPUT A.Balance
Supported built-in functions:
| Function | Description |
|---|---|
RIGHT(ThisString, x) |
Returns the rightmost x characters |
MID(ThisString, x, y) |
Returns a substring of length y starting at one-based position x |
LENGTH(ThisString) |
Returns the length of a string |
LCASE(ThisChar) |
Converts ASCII uppercase letters to lowercase; other characters are unchanged |
UCASE(ThisChar) |
Converts ASCII lowercase letters to uppercase; other characters are unchanged |
INT(x) |
Returns the integer part of a number |
RAND(x) |
Returns a random REAL in the range [0, x) |
EOF(file) |
Returns whether an open text file has reached end-of-file |
UNION(SetA, SetB) |
Returns the union of two sets |
INTERSECTION(SetA, SetB) |
Returns the intersection of two sets |
DIFFERENCE(SetA, SetB) |
Returns the elements in SetA but not SetB |
SYMMETRICDIFFERENCE(SetA, SetB) |
Returns elements in exactly one operand |
CONTAINS(SetValue, Element) |
Tests set membership |
CARDINALITY(SetValue) |
Returns the number of elements |
ISEMPTY(SetValue) |
Tests whether a set is empty |
ISSUBSET(SetA, SetB) |
Tests subset inclusion |
ISPROPERSUBSET(SetA, SetB) |
Tests strict subset inclusion |
ISSUPERSET(SetA, SetB) |
Tests superset inclusion |
ISPROPERSUPERSET(SetA, SetB) |
Tests strict superset inclusion |
ISDISJOINT(SetA, SetB) |
Tests whether two sets are disjoint |
Example:
OUTPUT RIGHT("ABCDEFGH", 3)
OUTPUT MID("ABCDEFGH", 2, 3)
OUTPUT LENGTH("Happy Days")
OUTPUT UCASE('h')
OUTPUT LCASE('W')
OUTPUT INT(27.5415)
Output:
FGH
BCD
10
H
w
27
Error classes are available from psei.errors:
from psei.errors import (
PseudoError,
LexError,
ParseError,
IncompleteInput,
PseudoRuntimeError,
)| Error type | Meaning |
|---|---|
LexError |
Lexical error, such as an invalid character or malformed literal |
ParseError |
Syntax error |
IncompleteInput |
Used by the REPL when a block is incomplete |
PseudoRuntimeError |
Runtime error, such as type mismatch, division by zero or array bounds error |
Example:
from psei import run_source
from psei.errors import PseudoError
try:
run_source("""
DECLARE X : INTEGER
X ← "not an integer"
""")
except PseudoError as error:
print(error)Create or update the locked development environment:
uv sync --lockedRun tests:
uv run --locked pytest -qRun the coverage baseline used by CI:
uv run --locked pytest --cov=psei --cov-branch --cov-report=term-missing \
--cov-fail-under=80Run the CLI from the project environment:
uv run --locked pseudo run examples/passing/declare_assign_output.pseudoThe committed .python-version selects Python 3.14 for local development,
while the package and CI remain compatible with Python 3.10 through 3.14.
In the Codex workspace sandbox, the default user cache may be read-only. Use a temporary writable cache and copy mode when synchronizing or running commands:
UV_CACHE_DIR=/tmp/psei-uv-cache UV_LINK_MODE=copy uv sync --locked
UV_CACHE_DIR=/tmp/psei-uv-cache UV_LINK_MODE=copy uv run --locked pytest -qThe first synchronization may also require sandbox network approval to download locked packages from PyPI.
The repository includes example programs:
examples/passing/
examples/errors/
examples/passing/ contains programs that should run successfully.
Each passing example has a matching .out file containing expected output.
examples/errors/ contains programs that should raise errors.
examples/errors/manifest.json records the expected error type for each error example.
psei/
├── examples/
│ ├── passing/
│ └── errors/
├── src/
│ └── psei/
│ ├── lexer.py
│ ├── parser.py
│ ├── ast_nodes.py
│ ├── analyzer.py
│ ├── compliance.py
│ ├── interpreter.py
│ ├── runner.py
│ ├── cli.py
│ ├── repl.py
│ ├── runtime/
│ │ ├── core.py
│ │ ├── environment.py
│ │ ├── files.py
│ │ ├── oop.py
│ │ ├── serialization.py
│ │ ├── types.py
│ │ └── values.py
│ ├── tokens.py
│ └── values.py
├── tests/
├── pyproject.toml
└── README.md
Main modules:
| File or directory | Purpose |
|---|---|
lexer.py |
Lexical analysis |
parser.py |
Parsing and AST construction |
ast_nodes.py |
AST node definitions |
analyzer.py |
Non-executing static semantic analysis |
compliance.py |
Cambridge compliance profiles and diagnostics |
interpreter.py |
AST execution |
runtime/core.py |
Runtime object, scopes and limits |
runtime/environment.py |
Variables, constants and references |
runtime/types.py |
Type system, coercion and cloning |
runtime/files.py |
Text and random file abstractions |
runtime/oop.py |
Class and object runtime structures |
runner.py |
run_source() and run_file() |
cli.py |
Command-line entry point |
repl.py |
Interactive REPL |
psei implements a practical Cambridge-style pseudocode subset and a
Cambridge 2027-2029 source compliance profile. It is not an official Cambridge
tool or a complete programming language implementation.
Not fully implemented:
- the full ADT library mentioned by the Cambridge syllabus, including:
- stack
- queue
- linked list
- dictionary
- binary tree
- presentation checks that cannot be inferred reliably from plain text, such as font choice and the alignment of wrapped continuation lines
- a prescriptive camelCase/PascalCase identifier-name checker; the current profile checks ASCII characters and consistent case-insensitive spelling
- flow-sensitive definite-assignment and interprocedural data-flow analysis
- process-level sandboxing
If you execute untrusted code, consider using:
- subprocess timeouts
- operating-system memory limits
- containers
- API-level request limits
- process isolation
Create hello.pseudo:
DECLARE Name : STRING
Name ← "Cambridge pseudocode"
OUTPUT "Hello, ", Name
Run it:
pseudo run hello.pseudoOutput:
Hello, Cambridge pseudocode