numskull version 1.2
The idea for Numskull was concieved the 11th of February 2022.
The idea was to make a programming language, where numbers weren't constant, but mutable. The first version of the interpreter was written over that same weekend (feb. 11-13 2022)
. Language features and syntax was decided as I was implementing them, and were not planned out from the start.
In this language there are no variables, and letters are forbidden. Only numbers and symbols are allowed. The language's gimmick is that all numbers are mutable, meaning their value can be reassigned, but contain themselves by default. This means all integer numbers, negative numbers, decimal numbers and decimal negative numbers can store values and are valid values to be stored.
All numbers in Numskull are stored and operated on as double precision (64bit) floating point numbers. The language only supports real numbers as input, but NaN and infinity values can be created during runtime (doing this is strongly discouraged, unless you know what you're doing).
Numskull programs are made up of individual instructions. Each line of code contains one instructions, and required newlines between them. An instruction is structured like this:
<lefthand> <operation> [righthand] [bracket]
Both lefthand and righthand are numbers, but righthand isn't required for all operations. Brackets are only a necessity for the comparison operations. Below are a few examples of some instructions:
10 = 60 //Assign a value
-5++ //Decrement a value
7.56 += 7 //Addition plus assignment
There are only a handful of valid operations in Numskull. These are:
-
Decrements value of lefthand. Usually doesn't work well with decimal numbers due to floating point weirdness.
Example:5++
would add1
to the value stored in5
. -
Increments value of lefthand. Usually doesn't work well with decimal numbers due to floating point weirdness.
Example:6--
would subtract1
from the value stored in6
. -
Assigns the value of lefthand to the value of the righthand.
Example:44.2 = -7
sets44.2
to be equal to the value of-7
. -
Adds the value of the righthand to the value of the lefthand and stores the result in the lefthand.
Example:5 += 2
adds the values of5
and2
together, and stores the result in5
. -
Subtracts value of the righthand from the value of the lefthand and stores the result in the lefthand.
-
Same as
+=
, but multiplies. -
Same as
+=
, but divides. -
Outputs the number stored in the lefthand as a string.
Example:17!
will output the string "17
". -
Outputs the number stored in the lefthand as a unicode character.
Example:32#
will output a space character. Look up an ascii/unicode table for the characters you wish to print. -
Reads a number from the input and stored it in the lefthand. The number read can be either from a file, either as binary or text, or can be input via the commandline.
Example:-60"
reads a value and stores it in-60
. -
Calls the function stored at the location of the lefthand. If the lefthand does not contain a function, the program will crash. More information about functions can be found here.
Example:99()
will call the function stored in99
. -
Requires a bracket at the end of the instruction. Tests the value of the lefthand compared to the value of the righthand. If the condition is not met, the program jumps to the next closing bracket of matching type at the same level and continues executing from there. Further documented in the next chapter.
Numskull supports branching using the comparison operator, as shown above. The different kinds of comparisons possible are the following:
-
Check if values are equal. Is true if the value of the lefthand is equal to the value of the righthand.
-
Check if values are different. Same as
?=
, but is true if the values AREN'T the same. -
Is true if the value of the lefthand is greater than the value of the righthand.
-
Is true if the value of the lefthand is greater than or equal to the value of the righthand.
-
Is true if the value of the lefthand is lesser than the value of the righthand.
-
Is true if the value of the lefthand is lesser than or equal to the value of the righthand.
A comparison operator requires an opening bracket after the righthand operator. The bracket should be on the same line as the condition instruction. If a condition isn't met, the program skips ahead to the next closing bracket of the same type at the same depth, and continues execution from there. In that way, the comparison operator acts like an equivelant to the if
-statement in C. Closing brackets should always be on their own line, separate from any instructions.
Below are two example programs and their outputs, to hopefully demonstrate how the comparison instruction works:
//Example program 1
10 ?= 0 { //Is 10 equal to 0?
10 = 60 //Set 10 to 60
10! //Print value of 10
10!
10!
} //End of if-statement
20! //Print value of 20
Output:
60606020
//Example program 2
10 ?< 5 { //Is 10 below 5?
10 = 40 //Set 10 to 40
10! //Print value of 10
10!
10!
} //End of if-statement
20! //Print value of 20
Output:
20
Loops can be constructed using the comparison operator instead, by simple using square brackets []
instead of curly brackets {}
. When a closing square bracket is encountered ( ]
), the program skips back up to the matching opening brackets condition statement, and continues from there. If the condition is still true, the loop is run again. Otherwise the program skips to the closing bracket and continues from there. Brackets only close each other, meaning a ]
is required to close a [
. The same applies to }
and {
.
Below is an example program and its output, to demonstrate how a loop works:
1 = 10 //Set 1 to 10
1 ?> 5 [ //Is 1 greater than 5?
1! //Print contents of 1
32# //Print a space
1-- //Decrement 1
]
Output:
10 9 8 7 6
The lefthand operator supports chaining using the +
and -
signs, but the way this works might not be obvious.
The leftmost value is the base, and is read as an immediate value, and not the value contained in it. Every link in the chain thereafter is not immediate, and the value added or subtracted will instead be the value of said number. Therefore it can be useful to keep 0 free, and use that as a base, if you don't care for a base offset.
<base> [+/- offset] [+/- offset]...
It is easier to illustrate using examples:
5+8+6
sets the lefthand to be equal to 5 plus the value at 8 plus the value at 6.5.5 - -7
sets the lefthand to be equal to 5.5 minus the value of -7. (Important: When chaining by subtraction, whitespace is always reqired between the chain operator and the number itself.)
Below is an example program to better explain lefthand chaining:
1 = 10 //Set 1 to 10
6+1! //Print value at (6+10) = 16 (1 contains 10)
32# //Print space
6+1+7! //Print number at (6+10+7) = 23
Output:
16 23
The righthand cannot be chained, and must always be just one number.
Introduced in version 1.2, Numskull supports functions. A function is a piece of code you define once, and can then jump to whenever you want.
Functions in Numskull are declared like so:
<lefthand> = <
[instructions...]
>
When a function has been declared, it can be called with the function call operator mentioned earlier. When a function is called, code execution jumps to the called function, and continues execution from there. Execution starts at the function start bracket <
, and returns to where the function was called, when the function end bracket >
is encountered.
Do note: If a function end brace is encountered WITHOUT any function being called, the program will crash.
-
As of version 1.1, Numskull supports code comments. A comment can be started using
//
, and anything that comes after it on the same line will be ignored. Comments can also be multilined. These are started with/*
and terminated with*/
. Anything between the two will be ignored at runtime. -
The interpreter does not care if brackets are opened and closed out of pairs. This means you could use brackets in the following order:
{ [ } ]
. If the first{
is taken, it jumps to the}
, inside the body of the[ ]
, and execution continues as normal from there, and when the]
is encountered, the condition at the[
is evaluated as normal. The same technique could be used to exit the body of the[ ]
early.
The interpreter is commandline based, and will likely remain so.
For more information on how it works and what options are available, run the interpreter from the commandline with --help
, or check the usage document.
This project is maintained on GitHub.