We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

Ψ++

From Esolang
Jump to navigation Jump to search

Ψ++ is a programming language designed by PSTF in Timeline 1 and by Kōnstantinos Sekitharios (a 30-year-old Greece man who is also very interested in cultures around the world) in Timeline 284436.

It's an equivalent to C++.

Volume 1: Basic Syntax

Our First Program

$import?io
using namespace std;

function main(int argC, string argV) @ signed
{
    print("Hello, Psi++!");
    return 0;
}

Chapter 1: Structure of Program

If a craftsman wants to do good work, he must first sharpen his tools. — Chinese Proverb

Section 1: Structure

First of all, every program will include one or several header files. These header files usually contain many things that are important or useful to the program. There is an old saying in Chinese civilization, 'If a craftsman wants to do a good job, he must first sharpen his tools.' Without these header files, you might have to start defining everything from scratch, and in the end, less than one percent of the program that actually runs is related to the program itself, and you might not even know where the problem lies.

Next is namespaces. A namespace indicates in which context the included header file should operate. For example, if you want an A4 sheet of paper but only say 'I want a sheet of paper,' you might end up with a B5 sheet or something like that.

Next is the program entry point—the main function. This function usually should return an integer, corresponding to the concept that exit codes are limited signed integers. Unless a program entry is specified, program execution generally starts from the main function.

In the main function, besides running the program, there is also a line of code used to return a specific value. Generally speaking, if the program has no exceptions, it should return 0.

Section 2: Compile and Execute

In general, source code should be saved to a file. The file extension for Ψ++ is psipp. The main compiler for Ψ++ is SygkentronoSynSyn (commonly known as the 'Three S Compiler'). When you want to compile a program, you can type sss hello.psipp in the command line. If there are no errors in the code, the command prompt will move to the next line.

E/Psipp/program=> sss hello.psipp
E/Psipp/program=> hello.exe
Hello, Psi++!

Section 3: Statements

Ψ++ uses a semicolon to end statements. A single line can contain several statements, because Ψ++ does not recognize the end of a line.

A block of statements is a series of statements enclosed in curly braces.

Chapter 2: Data Storage

The principle that can be spoken of is not the eternal principle; the name that can be named is not the eternal name. —— [Pre-Qin] Laozi

Section 1: Identifier

Ψ++ identifiers are used to name variables, functions, classes, modules, or any other user-defined items. An identifier starts with a non-digit valid identifier character (including Latin letters, underscores, and digits, etc.), followed by zero or more valid identifier characters.

Punctuation characters such as @, &, and % are not allowed in Ψ++ identifiers, but characters from other languages, such as ŋ, τ, ъ, and あ, are allowed. Ψ++ is a case-sensitive programming language. Therefore, in Ψ++, Manpower and manpower are two different identifiers.

Section 2: Data Types

Ψ++ provides a rich set of data types and allows users to define their own data types. Below is a list of commonly used data types:

Data Types
Name Range
int Z
short [-32768, 32767]
signed Depends on the bit width specified by the platform.
long [-215, 215-1]
long long [-231, 231-1]
unsigned Modifier, indicates that the data has no sign (all numbers are positive or 0).
float R
single [-3.4×1038, 3.4×1038]
fraction Q
double [-1.7×10308, 1.7×10308]
char [0, 255]
rune Depends on the latest Unicode standard.
string No range limitation.
bool True/False
none
auto Automatically inferred based on the type of the value or expression.
std~tuple<type, type[, type, ...]> Tuple.
array<type>[length] Array.
std~vector<type> Dynamic Array.
std~pair<type, type> Ordered Pair.
std~map<type, type> Map.
std~set<type, type> Set.

Section 3: Typedef

You can also use the typedef keyword to define an existing data type as another name.

typedef type alias

Section 4: Variables

Variable definition is to tell the compiler where to create the storage of a variable and how to create the storage of the variable. A variable definition specifies a data type and includes a list of one or more variables of that type, as follows:

type variable_list [<- value_list];

Here, type must be a valid Ψ++ data type, which can be an existing type or any user-defined object. The variable_list can consist of one or more identifier names, with multiple identifiers separated by commas.

If you want to initialize a variable, just add the content in the square brackets. If you do not specify an initial value, a variable with static storage duration will be implicitly initialized to NULL (all bytes are zero), and the initial value of all other variables is empty. For example, an integer will be implicitly initialized to 0, while a string will be implicitly initialized to an empty string.

Variable declarations assure the compiler that a variable exists with a given type and name, allowing the compiler to continue further compilation without needing to know the full details of the variable. Variable declarations are meaningful only at compile time; during program linking, the compiler requires the actual variable definition.

When you use multiple files and define a variable in only one of them (the file defining the variable is available at program linking), variable declarations become very useful. You can declare a variable anywhere using the "declare" keyword. Although you can declare a variable multiple times in a Ψ++ program, a variable can be defined only once in a file, function, or code block.

Section 5: Expression

There are two types of expressions in Ψ++:

  • Lvalue: An expression that refers to a memory location is called an lvalue expression. Lvalues can appear on either the left or right side of an assignment operator.
  • Rvalue: The term rvalue refers to a value stored at some address in memory. Rvalues are expressions that cannot be assigned to, meaning that rvalues can appear on the right side of an assignment operator, but not on the left side.

Variables are lvalues, so they can appear on the left side of an assignment operator. Numeric literals are rvalues, so they cannot be assigned to and cannot appear on the left side of an assignment operator. Below is a valid statement:

int constantine <- 114514

While this is invalid that will cause a compile error:

114514 <- 1919810

Section 6: Scope

Generally, there are three places where variables can be defined:

  • Variables declared within a function or a code block are called local variables.
  • The variables declared in the definition of function parameters are called formal parameters.
  • Variables declared outside all functions are called global variables.

Scope is a region of a program, and variable scope can be divided into the following types:

  • Local scope: Variables declared inside the function have local scope, which can only be accessed within the function. Local variables are created each time a function is called and destroyed after the function finishes execution.
  • Global Scope: Variables declared outside all functions and code blocks have global scope, and they can be accessed by any function in the program. Global variables are created at the start of the program and destroyed at the end of the program.
  • Block scope: Variables declared inside a code block have block scope, and they can only be accessed within the block itself. Block scope variables are created each time a code block is executed and destroyed after the block is executed.
  • Class scope: Variables declared within the class have class scope, and they can be accessed by all member functions of the class. The lifecycle of class scope variables is the same as that of the class.

Note: If a variable declared in internal scope shares the same name as one in external scope, the variable in internal scope will overwrite the variable in external scope. To access an external scope variable with the same name as the internal scope, you can add the global`` flag before the variable name.

Global variables are implicitly initialized to null values. If local variables are not explicitly initialized, then during implicit initialization a random number will be assigned, such as 114514.

Section 7: Constant

Constants are fixed values that do not change during the execution of a program. These fixed values are also called literals.

Constants can be of any basic data type and can be divided into integer numbers, floating-point numbers, characters, strings, and boolean values.

Constants are like regular variables, except that the value of a constant cannot be modified once it is defined.

You can declare a constant by adding 'immutable' before the type when declaring a variable, or by using the $macro preprocessor to 'define' a label as a value.

Although the naming conventions for constants are the same as for variable names, I personally suggest naming constants in all uppercase letters with underscores, rather than all lowercase or camel case.

Chapter 3: Control Flow

All is one, oh light!
One is all, oh light!
Light is you, light is me;
Light is "He," light is fire!
—— [Modern] Guo Moruo, "The Phoenix Nirvana"

Section 1: Loops

We often need to execute a segment of code over and over. In general, statements are executed sequentially: the first statement in a function is executed first, followed by the second statement, and so on.

Programming languages provide various control structures that allow more complex execution paths.

For-loop

For example, this is an example of an iterative loop.

for(int i <- 0; i < 10; i++)
{
    print(i);
}

While-loop

And the conditional loop.

while(condition) {dosomething}
do {dosomething} while(condition);

Loop Controlling

break

Terminate a loop or switch statement, and the program flow will continue executing the statement immediately following the loop or switch.

jumpover

Causes the remaining part of the loop body to be skipped and immediately restarts testing the condition or refreshing the loop-controlling variable.

retry

Causes the remaining part of the loop body to be skipped, but does not re-test the loop condition or refresh the loop-controlling variable.

Infinite loop

Any loop without a termination condition or whose termination condition is always true is called an infinite loop. For example, while(true), for(int i <- 0; i != 1; i += 2), etc., are all infinite loops.

Section 2: Conditional Jump

A decision structure requires the programmer to specify one or more conditions to be evaluated or tested, as well as the statements to execute when the condition is true (required) and the statements to execute when the condition is false (optional).

If-elif-else

if (condition1) {code1} else if (condition2) {code2} else {code3}

code1 is executed only when the condition in the if statement block is true; otherwise, continue to evaluate the conditions. If none of the above conditions are met, execute the statements in the else block. There must be exactly one if statement, zero or one else statement, and zero/one/multiple else-if statement.

Switch-case-default

switch(expression)
{
    case constant-expression:
        statement(s);
        break; // optional
    case constant-expression:
        statement(s);
        break; // optional

    // You can have any number of case statements
    default: // optional
        statement(s);
}

The switch statement must follow the following rules:

  • The expression in a switch statement must be a constant or enumeration type, or a class type where the class has a single conversion function that converts it to an integer or enumeration type.
  • A switch can have any number of case statements. Each case is followed by a value to compare and a colon.

The constant-expression of a case must have the same data type as the variable in the switch and must be a constant or a literal.

  • When the tested variable is equal to the constant in a case, the statements following that case will be executed until a break statement is encountered.
  • When a break statement is encountered, the switch terminates, and the control flow jumps to the next line after the switch statement.
  • Not every case needs to contain a break. If a case statement does not contain a break, the control flow will continue to the subsequent cases until a break is encountered.
  • A switch statement can have an optional default case at the end of the switch. The default case can be used to perform a task when all the above cases are false. A break statement in the default case is not required.

? expression

The general form of the ? expression is as follows:

Exp1 ? Exp2 : Exp3;

where Exp1, Exp2, and Exp3 are expressions. Note the use and position of the colon.

The value of the ? expression is determined by Exp1. If Exp1 is true, the value of Exp2 is evaluated, and the result becomes the value of the entire ? expression. If Exp1 is false, the value of Exp3 is evaluated, and the result becomes the value of the entire ? expression.

Chapter 4: Functions and Lambda

Nothing in the world is difficult for one who is determined. — Chinese Proverb

A function is a set of statements that execute a task together. You can divide your code into different functions. How to divide the code into different functions is up to you, but logically, the division is usually based on each function performing a specific task.

A function declaration tells the compiler the function's name, return type, and parameters. A function definition provides the actual body of the function.

Section 1: Function Definition

The general format to define a function is like this:

function name(parameter_list) @ returning type
{
    body of the function
}

In Ψ++ (Psi Plus Plus), a function consists of a function header and a function body. Below are all the components of a function:

  • Return type: A function can return a value. The return_type is the data type of the value returned by the function. Some functions perform the required operations without returning a value, in which case the return_type is the keyword none.
  • Function name: This is the actual name of the function. The function name together with the parameter list constitutes the function signature.
  • Parameters: Parameters act as placeholders. When the function is called, you pass a value to the parameter, which is called an actual argument. The parameter list includes the type, order, and number of function parameters. Parameters are optional, meaning a function may have no parameters.
  • Function body: The function body contains a set of statements that define the tasks performed by the function.

Section 2: Function Declaration

A function declaration tells the compiler the function's name and how to call the function. The actual body of the function can be defined separately.

A function declaration includes the following parts:

function_name( parameter list ) @ return_type;

When declaring a function, the names of the parameters do not need to be considered, so you can also declare the function like this:

function_name( type list ) @ return_type;

When you define a function in one source file and call the function in another file, a function declaration is necessary. In this case, you should declare the function at the top of the file where it is called.

Section 3: Function Call

When a program calls a function, the control of the program is transferred to the called function. The called function performs the defined tasks, and when the function's return statement is executed, or when the end bracket of the function is reached, the control of the program is handed back to the main program.

When calling a function, pass the required parameters, and if the function returns a value, the return value can be stored.

Section 4: Parameters

If a function is to use parameters, it must declare variables to receive the parameter values. These variables are called the function's formal parameters.

Formal parameters are like other local variables within the function: they are created when entering the function and destroyed when exiting the function.

When calling a function, there are three ways to pass parameters to it:

Function Calling
Call Type Description
Call by Value This method assigns the actual value of the parameter to the function's formal parameter. In this case, modifying the formal parameter inside the function does not affect the actual parameter.
Call by Pointer This method assigns the address of the parameter to the formal parameter. Inside the function, this address is used to access the actual parameter needed in the call. This means that modifying the formal parameter will affect the actual parameter.
Call by Reference This method assigns the reference of the parameter to the formal parameter. Inside the function, this reference is used to access the actual parameter needed in the call. This means that modifying the formal parameter will affect the actual parameter.

By default, Ψ++ uses call by value to pass parameters. Generally, this means that code within the function cannot change the parameters used to call the function.

When you define a function, you can specify default values for each of the later parameters in the parameter list. When calling the function, if the actual argument value is left empty, this default value will be used.

This is done by using the assignment operator to assign values to parameters in the function definition. When calling the function, if a value is not passed for a parameter, the default value will be used; if a value is specified, the default value will be ignored and the passed value will be used.

Chapter 5: Array

A single arrow can be easily broken, but a whole quiver of arrows cannot be crushed even by an elephant. — [???] Anonymous

Ψ++ supports array data structures, which can store an ordered collection of elements of the same type with a fixed size. Arrays are used to store a series of data, but they are often considered as a series of variables of the same type.

The declaration of an array is not declaring individual variables like number0, number1, ..., number99, but declaring an array variable, such as numbers, and then using numbers[0], numbers[1], ..., numbers[99] to represent individual variables. Specific elements in the array can be accessed via indexes.

All arrays are made up of contiguous memory locations. The lowest address corresponds to the first element, and the highest address corresponds to the last element.

Section 1: Declaration

type ArrayName[ArraySize];

This is the general form of declaring a one-dimensional array. Here, ArraySize must be a positive integer or a constant expression that returns a positive integer, and type can be a built-in variable type in Ψ++ or a user-defined structure.

For example,

int Numberlist[30001];

This statement will declare an array called Numberlist. The data inside it are all integers, and it can store up to 30,001 pieces of data.

Section 2: Definition and Representation

The way to represent an array is to enclose all the data in curly braces and then separate the data with commas.

To access a certain element of an array, you need to reference the array name, followed by a constant expression in square brackets. It cannot be greater than or equal to the array length, nor can it be negative — indexing starts from 0, so index 1 is actually the second element.

Section 3: Multi-dimensional Array

In addition to one-dimensional arrays, we can also define arrays with more dimensions. The simplest form of a multi-dimensional array is a two-dimensional array. The number of dimensions of the array you want to declare determines how many pairs of square brackets you need to put after the array name in the declaration statement.

Chapter 6: Pointer

Forest,
give me a leaf facing north,
for light, for youth,
for life that never fades.

Forest,
give me a leaf facing south,
for darkness, for old age,
for death that cannot be revived.

We will fill our souls
with green leaves...
Forest,
give leaves to all,
so that I may sing.

Or better yet, you sing yourself—
with my soul.

—[Romania] Sorescu

Many languages that support arrays or memory manipulation involve pointers. Pointers can simplify a series of code and can also perform memory allocation tasks that static arrays cannot do.

Section 1: Address

As you know, each variable has a memory location, and each memory location defines an address that can be accessed using the ampersand (&) operator, which represents an address in memory.

Section 2: Define of Pointer

type |>pointer_name

Here, type is a valid type, and pointer_name is a valid identifier. The pointer can point to any object of type type.

The actual data type of the value of all pointers, whether it is integer, floating-point, character, or other data types, is the same, all are long hexadecimal numbers that represent memory addresses. The only difference between pointers of different data types is that the type of the variable or constant they point to is different. When using pointers, the following operations are frequently performed: defining a pointer variable, assigning a variable's address to the pointer, and accessing the value at the address available in the pointer variable. These are done by using the unary operator |> (which we call the right-pointing triangle) to return the value of the variable located at the address specified by the operand.

Section 3: Pointer VS Reference

A reference variable is an alias, which means it is another name for an existing variable.

Once a reference is initialized to a variable, the reference name or the variable name can be used to refer to the variable.

A reference must be initialized when defined, and once it is bound to a variable, it cannot be bound to another variable.

For example,

int &konstantinos <- a

is equivalent to binding konstantinos to a.

References are easily confused with pointers, and there are four main differences between them:

  • There is no such thing as a null reference; a reference must be connected to a valid memory location. Because of this difference, references do not have "null references" or "dangling references."
  • Once a reference is initialized to an object, it cannot be made to refer to another object. A pointer can point to another object at any time.
  • References must be initialized when created. Pointers can be initialized at any time.
  • The object a reference refers to must be a variable, while a pointer must store an address.

Chapter 7: I/O

Buildings have their ancient and modern forms, the land has its north and south, characters undergo changes, sounds experience shifts; these are also inevitable trends.
— [Ming] Chen Di

Input and output are very important concepts. You may have noticed that in our previous programs we included the io header file, which is used for I/O.

Section 1: io.head

This thing provides something about regular I/O.

print(*args, **kwargs, sep <- " ", width <- 1, end <- "\n")

This function prints a serie of values to screen.

input()

This function is used for a value determined by user input.

scanf("format", **args)

This function takes a series of inputs in a specified format and transfers them to the address of the corresponding variable.

talkf("format", *args, **kwargs)

This function prints a serie of values in determined values.

errorprint("Error message")

This function outputs into stderr instead of stdout.

logprint("Debug Log")

This function outputs into the buffer of stderr.

read(*args, sep <- " ", end <- "\n")

This function just takes a series of inputs.

printLn("Line")
readLn(arg)
stdScan(end <- EOF)

Literally.

Section 2: Format your output

In the timeline 1, Ψ++ has the iomanip header file, but in Ψ++, all the methods for formatting input and output are integrated into the io header file.

That's all.

Chapter 8: Structural Data Type

Thinking of it, she casually dug up a clump of yellow mud from the edge of the pond, mixed it with water, and kneaded it in her hands, kneading it, kneading it until it became a little thing that looked like the first doll.

She placed this little thing on the ground. Strangely enough, as soon as this mud-made little guy touched the ground, it immediately came to life and, the moment it opened its mouth, shouted: 'Mom!' Then came a series of excited jumps and cheers, showing the joy of gaining life.
— From ancient Chinese traditional mythology, compiled by Yuan Ke [Modern Era]

Whether it's Ψ or Ψ++, you can bundle several properties of different data types together, give them methods, and then give such a complex data type a name. That's what a struct is, and it also falls under the category of types.

Section 1: Definition of struct

A struct is a user-defined data type used to combine different types of data together. Similar to a class, a struct allows you to define member variables and member functions.

To define a struct, you need to use the struct statement. The struct statement defines a new data type that contains multiple members, and the format of the struct statement is as follows:

struct structName {
    type name;
    type name;
    type name;
    ...
    function name(parameter_list) @ returning type
    {
        body of the function
    }
    function name(parameter_list) @ returning type
    {
        body of the function
    }
    ...
} object_names;

Here, structName is the name of the struct type, and type name is a standard variable declaration, like int i; or float f; or any other valid variable declaration. At the end of the struct definition, just before the last semicolon, you can specify one or more struct variables, which is optional.

Advantages of structs:

  • Simple data encapsulation: Suitable for encapsulating various types of simple data, usually used for data storage.
  • Lightweight: Compared to classes, struct syntax is simpler, suitable for small data objects. But for large data objects in complex projects, I personally recommend using classes.
  • Object-oriented support: Supports constructors, member functions, and access control, allowing for object-oriented design.

Section 2: Access to struct

To access the members of a structure, we use the member access operator (.). The member access operator is a dot between the structure variable name and the member we want to access, working kind of like the in Chinese.

You can pass a structure as a function parameter, and the way you pass it is similar to other types of variables or pointers. You can initialize a struct through a constructor, and you can also pass it by reference to avoid unnecessary copies.

Section 3: Terminologies of struct

  • struct keyword: Used to define a struct, it tells the compiler that what follows is a custom type.
  • Member variables: Member variables are the data items defined inside a struct, and they can be any basic type or other custom types. In a struct, these members are public by default and can be accessed directly.
  • Member functions: Structs can also contain member functions, which makes them functionally similar to classes. Member functions can manipulate the struct's member variables, providing encapsulation and operations on the data.
  • Access control: Like with classes, you can use public, private, and protected in a struct to set member access levels. In a struct, all members are public by default, whereas in a class, members are private by default.

You can also define pointers to a structure, similar to how you define pointers to other types of variables.

Section 4: Typedef for struct

Here's an easier way to define a structure, where you can give the type you create an 'alias'.

typedef struct structName {
    type name;
    type name;
    type name;
    ...
    function name(parameter_list) @ returning type
    {
        body of the function
    }
    function name(parameter_list) @ returning type
    {
        body of the function
    }
    ...
} alias;

Chapter 9: Dynamic Array

The only thing that is constant in this world is change.
— [America] Spencer Johnson

A vector is essentially a dynamically resizable array that can grow automatically. Compared to traditional arrays, a vector doesn’t require manual memory management and can expand based on the number of elements.

Since vectors have:

  • Contiguous memory storage
  • Fast random access
  • Full STL ecosystem support
  • Cache friendliness

they are usually the go-to choice for most sequential data storage scenarios in modern Ψ++ development.

Section 1: Use vector in your program

To use vectors, you need to include the header file vector.

$import?vector

Vector represents like an array, and is defined like this way.

std`:vector(int) vec[10] <- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

You can use push_back or emplace_back to push a thing to the tail of vector.

vec.push_back(100);
vec.emplace_back(200);

Or insert something to vector.

vec.insert(vec.begin(), 100);

You can access an element by both subscripts and at method.

int x <- vec[1]
int y <- vec.at(2)

You can also get the size of or the memory allocated for the vector.

vec.size()
vec.capacity()

You can go through a vector using an index number, an iterator, or a range-based for loop.

You can also use the erase() method to remove a specified element. When a vector deletes a middle element, the elements after it will all move forward.

You can clear the vector by clear() method.

Section 2: Expansion

When a vector runs out of space, it will expand:

  1. Allocate more memory
  2. Copy old elements
  3. Release old memory

This process is quite costly. To avoid frequent expansions, you can use the reserve method to allocate memory in advance.

This can reduce:

  • Memory reallocation
  • Element copying
  • Performance loss

This is a very important optimization technique in engineering development.

Section 3: Summary

Vector is one of the most important and commonly used data structures in modern Ψ++.

Its main advantages include:

  • Dynamic resizing
  • Contiguous memory
  • Fast random access
  • Cache friendly
  • Complete STL ecosystem

But you also need to watch out for:

  • Performance cost of resizing
  • Low efficiency when deleting in the middle
  • Iterator invalidation issues

For most sequential storage scenarios:

Vector is usually the default container of choice.

Volume 2: OOP Programming

Coming Soon.

Appendix 1: Examples

Coming Soon.

Categories