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/qc
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();
}
Copyright
- Author: Andre Prusinowski (Avris.it)
- Licence: MIT
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:
- if it's in quotes,
""
, it's a string, no matter what, - if it's numeric, we parse it as a number; having a dot, makes it a float:
7.
means7.0
,.7
means0.7
; and7
means7
, the integer - if it's in square brackets,
[]
, it's an array, recurreccy allowed, e.g.:[1 2 [a 3]]
.
Aside output
Apart from returning a single value, your QC program can produce also other type of data:
- standard output, printed out with
!
and¡
functions, - debug info: a step-by-step list of which token is handled now and how does the stack look at that point,
- list of test cases and their results: obviously only in testsuite mode.
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, and the libraries 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 do 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 |
$$filename |
Include file $filename |
Default variables
Default variable | Description |
---|---|
I |
$code |
C |
$input |
Z |
0 |
J |
1 |
T |
10 |
E |
"" |
S |
" " |
K |
"," |
D |
"." |
A |
[] |
L |
"abcdefghijklmnopqrstuvwxyz" |
U |
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
N |
"0123456789" |
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\StringToArray | 1 | Splits array $a letter by letter |
⥎ |
StringArray\ArrayToString | 1 | Joins array $a using string "" as a glue |
⥬ |
StringArray\CharToAscii | 1 | Returns ASCII value of a character $a |
⥪ |
StringArray\AsciiToChar | 1 | Returns character coresponding to given ASCII value |
ₓ |
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) |