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.

43

From Esolang
Jump to navigation Jump to search

43 is an esoteric programming language by User:Dragoneater67 made in 2026. It was designed to be very hard to implement. The name is derived from a decimal-to-hexidecimal conversion (43 is 67 in hexadecimal).

Overview

The language is high-level, it is in some ways quite similar to some non-esoteric programming languages (though it is still mostly esoteric). All data is stored in variables, the global deque, global queue, and the global stack. At the start of execution a fullscreen window is created where the program can draw. The window is initialized to be plain white.

Built-in types

43 has many built-in types, they're all immutable.

  • int8 -- Signed 8-bit integer.
  • int16 -- Signed 16-bit integer.
  • int32 -- Signed 32-bit integer.
  • int64 -- Signed 64-bit integer.
  • int128 -- Signed 128-bit integer.
  • bigint -- Signed unbounded integer.
  • uint8 -- Unsigned 8-bit integer.
  • uint16 -- Unsigned 16-bit integer.
  • uint32 -- Unsigned 32-bit integer.
  • uint64 -- Unsigned 64-bit integer.
  • uint128 -- Unsigned 128-bit integer.
  • ubigint -- Unsigned unbounded integer.
  • float -- Signed 32-bit floating point number.
  • double -- Signed 64-bit floating point number.
  • bigfloat -- Signed arbitrary precision floating point number.
  • ufloat -- Unsigned 32-bit floating point number.
  • udouble -- Unsigned 64-bit floating point number.
  • ubigfloat -- Unsigned arbitrary precision floating point number.
  • complex -- Complex number.
  • bool -- Boolean.
  • char -- Character.
  • string -- String.
  • func -- Function.
  • type -- Type.

Variables

Variables are used for storing data. This syntax is used for variable definition:

var name<type> = value

Between var and name, some keywords can be added to define the variable's properties:

  • mut makes the variable mutable, and immut makes the variable immutable.
  • local makes the variable local to its scope, and global makes the variable global.

Variables are local (with the exception of top-level variables, which are always global) and mutable by default. Mutable variables can skip intialization:

var name<type>

Mutable variables can omit type for dynamic typing:

var name

Variables can be accessed using their names:

name

And they can be reassigned:

name = value

Variable names can only contain uppercase and lowercase letters, numbers and underscores, they cannot start with numbers.

Arrays

Arrays are defined similarly to variables:

var name<type>[length] = { values }

Values inside arrays are comma separated. Length can be omitted if values are initialized:

var name<type>[] = { values }

Initialization can be skipped for arrays, but that requires specifying length:

var name<type>[length]

Arrays can be accessed and reassigned like variables. An element of an array is accessed like this:

name[index]

And is reassigned like this:

name[index] = value

Functions

There are 2 types of functions: anonymous functions and named functions.

Anonymous functions

Anonymous functions are defined with this syntax:

lambda(arguments) => returntypes {
  code
}

Arguments are separated by commas, each argument looks like this:

name<type>

Return types are also separated by commas, and they're just type names:

type

Functions can omit return types if they don't return anything:

lambda(arguments) {
  code
}

Functions can also omit arguments along with parentheses if they dont take arguments:

lambda {
  code
}

Arguments can omit types to accept any type:

name<>

Keywords mut/immut are applicable to arguments too:

mut name<>

Arguments are immutable by default. Each rettype must be substituted with ? if the return type can be anything. Functions return values using the return keyword:

return values

Here, values are comma separated. Functions can be called:

(function)(arguments)

Return values can be captured by assigning them to variables:

variables = function call

Here, variables are comma-separated.

Named functions

To create a named function, assign an anonymous function to a new variable:

var name<func> = function

Keywors that are applicable to variables are applicable to named functions to:

var global immut name<func> = function

A function can be assigned to a pre-existing variable if its of type func or dynamic:

name = function

A named function can be called:

name(args)

Types

Types can be created with this syntax:

var base<base type> name<type> = {
  variables
}

Here variables are comma separated. They represent properties of a type. Keywords global and local do not apply to properties. There are 3 property-specific keywords:

  • public means that it can be accessed from outside of the type's property functions.
  • private means that it can't be accessed from outside of the type's property functions.
  • inherit means that the property gets inherited by types that are based on the owner type.

Keyword base<base type> can be omitted if the type isn't based on any other type. Properties are public and inheritable by default

Here's an example property:

var private inherit name<type> = value

Properties can be functions too:

var public inherit name<func> = function

There are some property function names that have special meanings:

  • _init(this<typename>, value<>) called when an initialized variable of its type is created (var name<typename> = value). This function is supposed to initialize the variable, Here, the newly created variable of type typename (which is currently uninitialized) is passed as the first argument, and value is passed as the second argument.
  • _initnoval(this<typename>) called when an uninitialized variable of its type is created (var name<typename>). This function is supposed to initialize the variable, Here, the newly created variable of type typename (which is currently uninitialized) is passed as the first argument.
  • _opoperatior(this<typename>, other<typename>) => ? defines the behaviour of a dyadic operator. Here, operator is replaced by an operator (the list of which will be mentioned later). The left operand is passed as the first argument, and the right operand is passed as the second argument. For example, a + b is the same as a._op+(b).
  • _opoperatior(this<typename>) => ? defines the behaviour of a moandic operator. Here, operator is replaced by an operator (the list of which will be mentioned later). The operand is passed as the first argument. For example, ^a is the same as a._op^().

Operators

Only applicable to numbers.

  • a - b -- Subtract b from a.
  • a / b -- Divide a by b, round the result toward 0 if a and b are integers.
  • a * b -- Multiply a by b.
  • a > b -- Check if a is greater than b.

Only applicable to booleans:

  • a ^ b -- Exponentiate a by b.
  • a & b -- Logical AND a and b.
  • a | b -- Logical OR a and b.
  • ^a -- Logical NOT a.

Only applicable to strings and numbers:

  • a + b -- Add a to b.

Applicable to all types:

  • a == b -- Check a and b for equality.

Operations are executed in random order.

Control flow

The language has if/else statements, for loops and while loops (which also support else clause).

If statements

If statements use this syntax:

if condition -> lambda {
  code
} else -> lambda {
  code
}

This means that if condition is true, the former lambda function is called, else, the latter lambda function is called. Since the condition point to functions, they can use named functions instead of anonymous ones:

if condition -> func1 else -> func2

This means that if condition is true, func1 is called, else, func2 is called. Else clause may be omitted if it is unused:

if condition -> func

This means that if condition is true, func is called, else, nothing happens. The condition can be omitted too:

if -> func

This calls func unconditionally.

While loops

While loops use a syntax that is very similar to if statements:

while condition -> lambda {
  code
} else -> lambda {
  code
}

This means that the former lambda function is called in a loop while condition is true, and if condition was not true during the first check, the latter lambda function is called. Similarly to if statements, anonymous functions can be replaced with named ones and the else clause and even condition can be omitted. If condition is omitted, the function will be executed in an infinite loop.

For loops

For loops are used to iterate over elements in an array, they use this syntax:

for array -> lambda(immut el<>) => ? {
  code
}

Here, each element of the array is passed to the lambda function sequentially, the return value of the lambda function is the new value of the respective element. If array is immut, return types must be omitted:

for array -> lambda(immut el<>) {
  code
}

Just like if statements and while loops, the lambda functions shown in the examples can be replaced with named functions. Strings can be used in place of arrays.

Booleans

Booleans are reperesented by either of these 2 keywords:

true

or

false

Numbers

Numbers are written in base-6 with this alphabet:

012345

Floats use . for the point:

0.3

Floats can be written as fractions using the x//y notation:

1//2

The example above is equivalent to 0.3 in base-6. Number types with a bound wrap on overflow. All integer literals are bigint and all float literals are bigfloat.

Strings and Characters

Strings are wrapped in single quotes:

'This is a string.'

Characters are wrapped in double quotes:

"4"

Strings and characters use URL-encoding for escaping characters:

'This string ends with a newline.%0a'

Characters of a string can be accessed like elements of an array:

string[index]

Characters of a string can be reassigned too:

string[index] = value

Conversions

All conversions must be explicit. Only conversions from the table below are legal:

Conversion table
From To Note
Bigger number types Smaller number types Truncates the number if needed.
char string
Any number type other than complex complex
Integers Floating point numbers Truncates if needed.
Floating point numbers Integers Truncates if needed, rounds to nearest nearest 0.

Comments

Comments are wrapped in `:

`This is a comment.`

Comments can stretch across lines:

`This
is
a
multi
line
comment.`

Errors

All errors kill the program and all of its threads immediately. There's only 1 type of error:

Something went wrong.

Which occurs when something goes wrong.

Preprocessor

Preprocessor commands take up an entire line. They are structured like this:

%command args

Here, args are space-separated. Upon evaluation, the command's return value replaces the command itself. Here's a list of preprocessor commands:

  • use filepath -- Reads the file at the filepath and returns its contents.
  • def name(args) definition -- Similar to define from C. Creates a macro named name (the name can only contain uppercase latin letters, numbers and underscores, cannot start with numbers), with comma separated args, and definition, where each instance of an argument name gets replaced by the value passed to the macro.
  • ifdef name { code } -- Runs code if a macro named name exists.
  • ifndef name { code } -- Runs code if a macro named name does not exist.
  • undef name -- Delete a macro named name.

A preprocessor macro can be used like this:

NAME(args)

Here, args are comma-separated.

Built-in functions

43 has many built-in functions, they're all immutable.

Types

  • typeof(immut value<>) => type -- Get the type of a value.
  • typeconv(immut target<type>, immut val<>) => ? -- Convert a value to an another type.
  • strlen(immut str<string>) => bigint -- Get the length of a string.
  • arrlen(immut arr<>[]) => bigint -- Get the length of an array.

Data

  • inject(immut element<>) -- Inject an element into the global deque.
  • push(immut element<>) -- Push an element into the global deque.
  • pop() => ? -- Pop an element from the global deque.
  • eject() => ? -- Eject an element from the global deque.
  • enqueue(immut element<>) -- Enqueue an element into the global queue.
  • dequeue() => ? -- Dequeue an element from the global queue.
  • spush(immut element<>) -- Push an element into the global stack.
  • spop() => ? -- Pop an element from the global stack.

Control flow

  • die() -- Halt the program.
  • thread(immut function<func>) -- Create a new concurrent thread that executed the passed function.
  • wait(immut seconds<bigfloat>) -- Wait a few seconds.
  • exec(immut code<string>) -- Execute code.
  • append(immut line<bigint>, code<string>) -- Append code after a specified line, if the line is less than or equal to 0, the code gets prepended to the program.
  • proglen() => bigint -- Get program length in lines.

Graphics and audio

  • getw() => bigint -- Get screen width.
  • geth() => bigint -- Get screen height.
  • drawp(immut x<bigint>, immut y<bigint>, immut h<bigfloat>, immut s<bigfloat>, immut l<bigfloat>) -- Draw a pixel on the screen. Color is specified using the HSL color format.
  • beep(immut hz<bigfloat>, immut seconds<bigfloat>) -- Beeps for a few seconds.

Examples

Infinite loop

var entry main<func> = lambda(immut args<string>[]) => uint8 {
  while -> lambda {}
  return typeconv(uint8, 0)
}

XKCD Random Number

var entry main<func> = lambda(immut args<string>[]) => uint8 {
  var width_too_short<bool> = getw() > 3
  width_too_short = ^width_too_short
  var height_too_short<bool> = geth() > 3
  height_too_short = ^height_too_short
  if width_too_short | height_too_short -> die
  drawp(0, 0, 0, 0, 0)
  drawp(2, 0, 0, 0, 0)
  drawp(0, 1, 0, 0, 0)
  drawp(1, 1, 0, 0, 0)
  drawp(2, 1, 0, 0, 0)
  drawp(2, 2, 0, 0, 0)
  while -> lambda {}
  return typeconv(uint8, 0)
}

Computational class

43 is Turing-complete since it natively supports the lambda calculus. Here is the lambda expression (λx.xx)(λx.xx) written in 43 as an example:

var entry main<func> = lambda(immut args<string>[]) => uint8 {
  (lambda(x<func>) => func { return x(x) })(lambda(x<func>) => func { return x(x) })
  return typeconv(uint8, 0)
}