Pink is a functional stack based programming language. The programming language uses a stack to optionally store the result of each statement of the program. Each statement is made up of either a definition of a variable or an expression. Variables can either be defined globally, which can be accessed anywhere but only modified in the global scope, or locally in a function as a parameter to that function. Expressions are made up of values and operations on those values. A value can be anything from a function, to a variable, or to a plain number.
Pink is currently a work in progress and contains many bugs. Alot of work is needed.
You can run pink from your browser. No installation is required.
https://ellabellla.github.io/pink/www/
fact: (x:0) -> [x=0?( 1; x * (x-1) -> fact)];
debug|(10)->fact|;
fib: (x:0)->[
x=0?(0!;0);
x=1?(1!;0);
(x-1)->fib,
(x-2)->fib,
@+@
],
debug|(10)->fib|;
fib: (n:0;i:0;a:0;b:1) -> [
n=i?(
a!;
(n;i+1;b;a+b)->fib!
);
];
debug|(10)->fib|;
centreX: width/2;
centreY: height/2;
# <- (radius: 100) -> [@;
circle|@-centreX; @-centreY; radius|,
sdf|@|
];
centreX: width/2;
centreY: height/2;
# <- (_width: 100; _height:50) -> [@;
rect|@-centreX; @-centreY; _width; _height|,
sdf|@|
];
- better error reporting
- more tests
- unit tests
- create integration tests
- dynamic defaults for scope definition
A statement is a definition or an expression, and a terminator. A terminator either throws the result of the statement away or pushes it onto the stack.
1,pushes1onto the stack1*2;calculates1*2then throws the result away
A number is any floating point number. The dot point can be placed before, after or in the middle of the number. A - sign can be placed before to make the number negative.
0.00.0.0
Booleans are just numbers. 0.0 is false and anything is true.
trueis1.0falseis0.0
Identifiers are used to refer to variables or external calls. They start with a letter or underscore and can contain alphanumeric characters and underscores.
An indexed value is an expression that indexes an element in the output matrix. Indexing starts at 0.
#(1;1)gets the element on row one and column one
@pops the top of the stack@@peeks of the stack
A matrix is used represent the output image of the program. It stores only numbers. All values in the matrix are converted into a scale of 0.0 to 1.0, 1.0 being pink and 0.0 being black, when the program ends. Then the matrix is outputted as an image.
#refers to the output matrixwidthrefers to its widthheightrefers to its height
Strings are an list of characters surrounded by "s, An escape can be used to put special characters in the string. The escapes are:
\nfor a new line\rfor a carriage return\tfor a tab\\for a\\"for a"
A range defines a range of numbers from a starting number to an ending number. By default the step is one but can be optionally define. The starting number is inclusive, the ending number is not. They are used as parameters to extended functions.
{0;10}creates a range from 0 to 9 inclusive, stepping by 1- giving you
0 1 2 3 4 5 6 7 8 9
- giving you
{0;10;2}creates a range from 0 to 8, stepping by 2- giving you
0 2 4 8
- giving you
{10;0;-1}creates a range from 10 to 2, stepping by -1- giving you
10 8 6 4 2
- giving you
Definitions define variables. Variables can contain expressions and functions. Variables are defined by first writing the identifier, then the set operator :, and then an expression or function. Definitions can also be used to change the value of a variable. No type is needed to be given but once a variable is set the type of cannot changed.
var: 1+1;creates a variable calledvarthat contains2
The set operator, :, can be combined with other operators to perform an operation on the variable with the given expression and then set it to the result. This can only be done with numbers. The variable must already be defined before this can be done.
var+: 1;adds1tovarthen setsvarto the result.
Definitions evaluate to a reference to the variable that was created or modified.
The scope of a variable is defined by the function it is defined in. It may only be accessed from within that function except for global variables, variables defined out side of functions, which can be accessed anywhere. Global variables can only be modified in the global scope.
An expression is made up of values and operators. Values and operators are chained together to create an expression. The most simple expression is just a value 1. A more complex expression is two values being operator on 1+1.
Values can be:
- a number
- a boolean
- an indexed value
- a stack reference
- an variable pointing a function or a number
- a function
- an external call
notlogical not operator, is a unary operatornot 1becomes0.0
!return the result of the expression from the current function, or exit program if used in the global scope2+2!return 4 from current function- when used on an exec in a function, or a reference to an exec in a function, it will call the exec using tail recursion before returning
*multiply/divide
+add-subtract
<less than, resolves to true or false>greater than, resolves to true or false<=less than or equal, resolves to true or false>=greater than or equal, resolves to true or false=equals, resolves to true or false
andlogical and operator, resolves to true or falseorlogical or operator, resolves to true or falsexorlogical xor operator, resolves to true or false
ifconditional operator, takes an expression and a list containing two expressions surrounded by(and)and separated by;- the first element is evaluated if the condition is true and the second if false
In pink functions are first class. They can be set to variables and evaluated inline in expressions.
An exec is a simple function. It has a scope definition (a list containing expressions and definitions) and a body definition, containing a list of statements that are terminated, separated by ->. The function can access the parameters defined in the scope by name and the expressions passed by the stack in they order they were defined, last on top and first on the bottom. The parameters can be modified inside the function. All named exec's must have a defined scope. Anonymous exec's can omit the scope and the ->, and just define a body. Its scope will then be the containing scope.
(x:0)->[x+1];a exec that takes one param,x, and returnsx+1x:10; [x+1];an anonymous exec that omits it's scope and returnsx+1,xnow being a variable in the surrounding scope.
Variables containing a function can be called by simply using as a value, func + 1 resolves to the return of func plus 1, or by using a scope definition to pass parameters. Parameters are passed in the order they are returned and you can pass any number of them.
add:(x:0,y:0) -> [x+y]addreturns0()->addreturns0(1)->addreturns1(1,1)->addreturns2
Placing a ! after an exec will cause it to be called using tail recursion causing the calling function to be replaced.
A scope definition is a list containing expressions and definitions surrounded by ( and ) and seperated by ;. The definitions define the parameters. The expressions define the starting stack values.
(1; x: 0; 10; y:1)the starting stack will be1,10, with10at the top, and the function will have two parametersx, with a default value of0, andy, with a default value of1
The body is a list of statements surrounded by [ and ]. Each statement must be terminated except the last statement which by default will have it's resulting value pushed to the stack.
[1; 10, 11, @ + @]resolves to 21
Into is a function that calls a exec to populate the matrix. It can populate:
- a matrix
It is defined by writing matrix, then
<-, and then an exec or a variable containing an exec. #<-[10];will fill a the matrix with10s
The into passes the value already in the matrix then the index/x and y coordinate to the called exec by places them on top of its stack. For example,the first call of an into would have a stack that looks like 0 0, 0 0 being the index and 10 being the current elements value, followed by it's default starting stack values.
Into resolves to nothing.
For each is a function that calls a exec for each value of a collection of elements. It can iterate over:
- a range
- a matrix
A for each is defined by the collection, then
<*, and then the exec. {0;10}<*[@+1];will call the exec for each number from0to9
The for each passes the value in the collection then the index/x and y coordinate to the called exec by places them on top of its stack. For example,the first call of the into on the range {0;10} would have a stack that looks like 0 10, 0 being the index and 10 being the current elements value on the top of the stack, followed by it's default starting stack values.
For each resolves nothing, or the last return if it was iterating a range.
Reduce is a function that calls a exec for each value of a collection of elements and adds the value of the return of each call together iteratively. It can iterate over:
- a range
- a matrix
A reduce is defined by the collection, then
<*, and then the exec. {0;10}*>[@+@];will add the values from0to9together
The reduce passes the value in the collection, then the accumulation, and then the index/x and y coordinate to the called exec by places them on top of its stack. For example,the first call of reduce on the range {0;10} would have a stack that looks like 0 0 10, 0 being the index, 0 being the starting accumulation, and 10 being the current elements value on the top of the stack, followed by it's default starting stack values.
Reduce resolves to the final value of the accumulator.
External calls run functions that exist outside of the program. They are called by writing their identifier then an argument list surrounded by | and |, and separated by ;.
sin|10|;will call the external function sin with the argument 10
Some external calls can take an optional string as a parameter. The string is passed as the first argument with no separator followed by the rest of the arguments.
debug|"should be 10" @|passes the string"should be 10"and the value@to the calldebug
All external calls will resolve to some value.
sin- one param
cos- one param
tan- one param
floor- one param
ceil- one param
sqrt- one param
These external calls are used to calculate SDFs.
circlecalculates SDF of a circle- 3 params
- the current pixel x coord
- the current pixel y coord
- the radius
- 3 params
rectcalculates SDF of a rectangle- 4 params
- the current pixel x coord
- the current pixel y coord
- the width
- the height
- 4 params
translatetranslates a coordinate- 2 params
- the current pixel x/y coord
- the offset
- 2 params
scalescales a coordinate- 2 params
- the current pixel x/y coord
- the scale
- 2 params
rotateXrotate the x component of a coordinate- 3 params
- the current pixel x coord
- the current pixel y coord
- the rotation (0.0 no rotation, 1.0 full rotation)
- 3 params
rotateYrotate the Y component of a coordinate- 3 params
- the current pixel x coord
- the current pixel y coord
- the rotation (0.0 no rotation, 1.0 full rotation)
- 3 params
sdftakes the output of a SDF and returns the color output- one param
debug- one param and optional string
- prints value to the console
This software is provided under the MIT license. Click here to view.