QC Quite Concise Programming Language

This piece of software is in EARLY development stage

The idea is to create a simple (in terms of complexity, not ease of use) programming language, that will have quite a concise syntax, that will play with the power of Unicode and most importantly – will be fun to create ;)

Documentation

Full documentation is available at docs.avris.it

Sample code

This program returns the length of the Collatz sequence for a given number:

# Length of Collatz sequence
(⪑☯1:{Aa⇓↓a1>:aa2%a3*1+a2/▲=}A↹)I☯

@0 => 1
@1 => 1
@2 => 2
@3 => 8
@4 => 3
@5 => 6
@[0 1 2 3 4 5] => [1 1 2 8 3 6]

First line is, obviously, just a comment, second one is the actual code, and the rest are test cases – when you run your code in test mode, it will run for input 0 and check if the output is 1, then run for 1 and expect 1 as output, and so on. Notice, in the last case, how simply can a function be applied to every element of a whole array.

(⪑☯1:...) defines a function that takes 1 argument (= its arity is one) and allows our array trick ( switch).

{...:...} means: while the first part is true, keep executing part two.

A is a predefined variable containing an empty array. We put it on the stack.

a, b, c, d... are the parameters we passed to the function (here we only one one, a, because our arity is one).

We put the value of a (a⇓) on the stack.

Function takes two elements from the stack (i.e. A and a⇓) and puts a⇓ at the end of the array A.

We put on the stack the variable a and the number 1. Function > takes them off and compares. If a > 1, it puts 1 (true) back on the stack, and 0 (false) otherwise. So, that would be our while condition.

Then we have aa2%a3*1+a2/▲= which is basically a = a % 2 ? 3*a+1 : a/2, but written with Reverse Polish notation and with function acting as a teranry operator.

After the while loop is finished, we put the length of array A (A↹) on the stack. This is return value of our function. Of course, we could return the whole array (A) or its concatenated content (AS⥋).

I☯ means we put the input I on the stack, and apply our function to it.

Installation

Install it straight from Git repository or using Composer:

composer require avris/qc

Usage

Online

Online interpreter is available at qc.avris.it/try

In console

vendor/bin/qc '2 2+'                        # 4
vendor/bin/qc '2 2+' -d                     # 4, debug data displayed
vendor/bin/qc '2 I+' 5                      # 7
vendor/bin/qc '"foo""bar"+'                 # "foobar"
vendor/bin/qc -f demo/collatz.qc 5          # 6, source code was read from file
vendor/bin/qc -f demo/collatz.qc -s         # run related test suite
vendor/bin/qc -f demo/collatz.qc 5 -dl 300  # debug mode with increased lines limit

In PHP

try {
    $qc = new QC();
    $result = $qc->run($code, $input, new EchoOutput());
    echo 'Result: ' . $result;
} catch (QCException $e) {
    echo 'Error: ' . $e->getMessage();
}

Author

Getting started

Writing the QC interpreter was really fun! But writing its documentation is not gonna be so, I suppose... And hardly anybody is gonna read it anyway... So please, don't blame me for not being too thorough with it. I'll try to be concise here (nomen omen), but to still cover everything important.

QC's interpreter is written in PHP. Don't ask why. Please, don't.

Getting QC

Composer is a powerful package manager for PHP. It takes care of all the dependencies and stuff. To install it, simply run this command in your console.

curl -sS https://getcomposer.org/installer | php

Now you should have a composer.phar file. Run it with such parameters:

php composer.phar require avris/qc

And voilà! QC is ready to use. Btw, if you want to use some developer tools on it, install it with the flag --dev.

How it works

Well... It's all about the stack. If you're still reading at this point, you're probably one of those people, who already know, what stack is. And the Reverse Polish notation, of course.

We're using both.

Every variable and literal (string, number, regex), when it's read by the interpreter, gets put on stack. Every function, on the other hand, pops some given (fixed!) number of elements from the stack and executes some operation on them, then puting its result back on the stack

For instance: what we'd normally write down as (2 + 4) * 3, in QC is: 2 4+3*. We put 2 on stack, then 4, and then we execute + function. It takes two arguments from the stack, adds them together, and puts the result, 6 back. We put 3 on top of that, and then * function comes in, taking those two values off, but giving 18 back. That's what we end up with at the top of the stack, so that's our result.

Note how 2 and 4 are separated by a space. Otherwise it would be understood as one number, 24. The space wouldn't be necessary if, for instance, we had a variable that's equal to 4. Let's say I. Then our code becomes just 2I+3*.

Btw, I just happens to be a predefined variable equal to whatever you provided your program with as input data. We have a couple of predefined variables, all listed at the end of this documentation, in Reference section. Actually, a lot of other stuff is listed there. Take a look, really. I'm not gonna explain everything here.

All the english letters, upper- and lowercase, are QC's variables. For the functions, we use other characters: either normal stuff like + or %, or something more fancy, like , , . Get used to it.

I know, there could be much trouble regarding Unicode usage. Not every console supports it, some websites might break it, blah blah... Oh c'mon, Unicode isn't magic, it isn't some freaky non-standard conceit! It's not my fault, if some tools you might be using don't support the XXI century.

Input parsing

When providing some input straight to QC as a PHP value, everything is fine, it knows exaclty what you meant. However, in the console or online interpreter, things get a bit more complicated. Writing 2 doesn't tell QC, if you mean a number 2, or a string with one character "2". So, the deal is:

Aside output

Apart from returning a single value, your QC program can produce also other type of data:

Those types of data are handled by the AsideOutput object that you pass to $qc->run(). Of course, you can implement your own one, if you need to.

Sample programs

Example is always the best way to learn something. Let's take a look at a couple of them.

Hello World

"Hello World!"

That's it. We put a string literal on the stack. Whatever is at the top of the stack at the end of the execution, is our output. We could also print it on the screen, instead of just returning it, if we use the ! function:

"Hello World!"!

Champernowne

The Champernowne constant is equal to 0.1234567891011121314.... And the program below finds the first appearance of a given number (I variable) in its decimal expansion:

IT+₁E⥋IΦ‡

Some test cases:

@20 => 30
@333 => 56
@0 => 11     #because the zero before the dot doesn't count
@2930 => 48

So what happens there in the code? First, we add I to T: IT+. I is our input, let's say 17, and T is predefined variable equal to ten. So now we have 27 on the stack. The function creates a range from 1 to its argument. So now our stack top is an array: [1 2 3 ... 26 27]. The whole "adding ten" thing is necessary for the 0 case.

Function ab⥋ joins an array a using string b as a glue. Here, our glue is E – a predefined variable containing an empty string. So after joining, our stack top is a string: 1234...2627.

abΦ finds the position of the first occurance of b in the string/array a. In our case: the position of I=17 in string 1234...2627. Which is 23, because Φ starts indexing at 0. To get the 1-based response, we have to increment. 23‡ is 24 – and that's our answer.

Random walk

Our input is [m n], and our output should be something like that:

                   .
                     .
                   .
                   .
                  .
                .
                 .
                .
                  .
                .

We make m steps, each lasting n seconds, and we randomly move to the left or to the right either by -2, -1, 0, 1 or 2 fields. Going below 0 or above 39 is not allowed.

So, let's look at the code:

☉X20=a↪XX-2 2℥+Z39⩥=XSX*D+!bƶ↩·

is a shortcut function that takes an array (from the stack or, if it's empty, from the input), and assigns its values to variables a, b, c... and so on. So in our case, assuming we inserted the input [10 1], the function automatically assinges two variables: a=10 and b=1.

X20= – variable assignment; X is our current position from the left edge, and is equal to 20.

a↪...↩ – repeat ... for a times. Quite staight forward. We make a=10 steps.

-2 2℥ – random integer between -2 and 2.

X...+ – add the random number to our X variable.

abc⩥ – make sure a is between b and c. Here, we make sure that X is not less than Z=0 and not greater than 39.

X...= – we assign the result back to X.

SX*D+ – multiply S=" " times X (so we get X consecutive spaces), and then add D=".". "Add" means "concatenate", if we're talking about string, of course. In QC one function can do different things depending on the type of its arguments.

Now, we just print (!) our string and wait (ƶ) for b seconds. After the loop we put a null (·) on the stack, to make sure the program doesn't return anything.

Factorial

(⪑☯1:a¿aa1-☯*:1?)I☯

If you read the example in Readme, you should already be able to understand this code. What's different here, is that we use recursive calls. Easy thing, right?

Oh, and by the way: we don't have to calculate factorial like that. There's an in-build function for it:

I‼

Extending QC

Contribution

QC is open source, available on Gitlab. Feel free to check out the code, fork, sumbit pull requests, whatever. I'd appreciate!

Creating libraries for QC

QC is written in PHP, so the libraries will have to be as well. Library is just a set of functions that extend QC's default function set. Registering them is easy:

$qc = new QC()
$qc->registerFunction('☀', 'MyNamespace\Functions\Sun');
$qc->registerFunction('★', 'MyNamespace\Functions\Star');
$qc->registerFunction('❤', 'MyNamespace\Functions\Heart');
return $qc->run('"foo"5I2★/❤', $input, $asideOutput);

Those classes must extend AbstractFunction, i.e. define their arity, some description string, and what they actually do. They can do anything. They have access to the stack, all the variables, and the aside output.

Just take a look at what's in Avris\QC\Token\Func namespace, and to something similar.

Custom AsideOutput

Just implement AsideOutputInterface with whatever you need it to do. It's not hard, really.

Reference

Literals

Literal Description
· null
-12 integer
8. / 8.15 / .8 float
"13" / "a" string
[1 2 abc ["foo bar"]] array
<$pattern:$modifiers> Regular expression (matching)
<$pattern:$modifiers;$replacement> Regular expression (replacing)

Controls

Control Description
{$condition:$block} while ($condition) { $block }
$condition¿$ifBlock:$elseBlock? if ($condition) { $ifBlock } else { $elseBlock }
$iterator↪$block↩ for (1..$iterator) { $block }
foreach ($iterator) { $block } if $iterator is an array
($name$arity:$block) function $name ($arity) { $block }
Arity means number of parameters it takes from the stack
(⪑$name$arity:$block) Providing ⪑-function with an array as a parameter will apply the function to all elements of that array
Only works for arity 1 and 2
#comment Comment
@$input => $expectedOutput Test case

Default variables

Default variable Description
I $code
C $input
Z 0
J 1
T 10
E ""
S " "
K ","
D "."
A []

Constants

Constant
π

Functions

Function Class Arity Description
= Assign 2 $a = $b
¡ PrintFunc 1 Prints $a
! PrintNewLine 1 Prints $a and a new line
Pop 1 Pops one element from the stack
Ternary 3 $a ? $b : $c
All the expressions get executed. If you don't want that, use ¿:?
Value 1 Returns the value of $a
ArrayToVars 1 Iterates over $a (or I if stack is empty) and sets its values as variables a, b, c, ... consecutively
ƶ Sleep 1 Sleeps $a seconds
+ Math\Plus 2 $a + $b
- Math\Minus 2 $a - $b
* Math\Times 2 $a * $b
Or, if one argument is a string and the other is numeric, repeats the string given number of times
/ Math\Divide 2 $a/$b
% Math\Modulo 2 $a % $b
^ Math\Power 2 $a to the power of $b
Math\Sqrt 1 Square root of $a
Math\Log 2 Logarithm of a $a with respect to base $b
Math\Increment 1 $a++
Math\Decrement 1 $a–
Math\Add 2 $a += $b
± Math\Negate 1 -$a
Math\Abs 1 Absolute value of $a
Math\Floor 1 Round $a down
Math\Ceil 1 Round $a up
Math\Round 1 Round $a
Math\Factorial 1 $a factorial
Math\IsNumeric 1 Is $a numeric?
Math\Random 2 Generates a random integer between $a and $b
Math\Base 3 Converts $a in base $b to base $c
Math\LimitRange 3 If $a is outside of range ($b, $c), it gets moved to its boundaries
Math\Hypotenuse 2 Hypotenuse of triangle with altitudes $a and $b
Math\DegToRad 1 Convert $a degrees to radians
Math\RadToDeg 1 Convert $a radians to degrees
Math\Trig 2 Calculates $a trigonometrical function of value $b
Compare\Equals 2 $a == $b
Compare\NotEquals 2 $a != $b
> Compare\Greater 2 $a > $b
Compare\GreaterEqual 2 $a >= $b
< Compare\Less 2 $a < $b
Compare\LessEqual 2 $a <= $b
Logic\LogicalOr 2 $a or $b
Logic\LogicalAnd 2 $a and $b
Logic\LogicalXor 2 $a xor $b
~ Logic\LogicalNot 1 Not $a
StringArray\Length 1 Length of string or size of array
StringArray\PushTop 2 Put $b at the end of array $a
StringArray\PushBottom 2 Put $b at the beginning of array $a
StringArray\Sum 1 Sum of array's elements
StringArray\Product 1 Product of array's elements
StringArray\Explode 2 Splits array $a with divider $b
StringArray\Implode 2 Joins array $a using string $b as a glue
StringArray\Range 2 Generate a range of integers between $a and $b
StringArray\RangeOne 1 Generate a range of integers between 1 and $a
StringArray\RangeZero 1 Generate a range of integers between 0 and $a
StringArray\Reverse 1 Reverses an array
StringArray\FindIndex 2 $a[$b] or null
StringArray\FindSubrange 3 Subarray of $a starting with $b (negative $b means counting from the end) of length $c
StringArray\Min 1 Minimum of array's elements
StringArray\Max 1 Maximum of array's elements
StringArray\InArray 2 Checks if $a is an element of array $b
Φ StringArray\FindPosition 2 Finds the position of the first occurance of $a in string/array $b (or -1 if not found)
StringArray\AlmostEqual 2 Checks if string $a matches regex $b (commutative)
Sets the R variable to found matches
StringArray\NotAlmostEqual 2 Checks if string $a does not match regex $b (commutative)
Sets the R variable to found matches
StringArray\Transform 2 Transforms string $a according to regex $b (commutative)