This is a tutorial for the Chomik programming language.
Chomik is a young language, it's first implementation started in 03.04.2022. Yet it is quite powerful. It breaks off with the tradition of the stack-based imperative languages, which are based on the mathematical notation. Chomik is not a clone of C++ or Java. Instead Chomik proposes certain new ideas.
Let us begin traditionally with a Hello World program:
execute <print "Hello world">;
<print "Hello world">;
Like in many scripting languages in Chomik comments begin with the character "#" and last till the end of line.
###################################################################
# This is a comment
# Another comment
###################################################################
<print "Hello world">; # We could also write a comment after code.
Chomik has several built-in types. Among others - the integer type.
variable x:integer;
We can also declare multiple variables separating them with commas:
variable x:integer, y:integer, z:integer;
In order to assign a simple variable we need to apply the let instruction.
variable x:integer;
let variable x=value integer 123;
We can also omit the "variable" keyword after let and write:
variable x:integer;
let x=value integer 123;
Note that the assigned value is explicitly denoted as an integer value. The unary minus is supported.
When we want to assign a variable with a different variable value we can achieve this using the <> operator:
variable x:integer,y:integer;
let x=value integer 123;
let y=<x>;
Why the strange operator? <>? Isn't it enough to use the variable name? In Chomik we must use this operator to access the variable value. So what about "print"? Why do we enclose it in <>? Do we mean that "print" is also a variable? Well, in Chomik "print" is a built-in family of variables. You must have some patience, we will explain the concept.
We can use the <> operator to access the variable value. We can declare it, assign and print out its value with the following code:
variable x:integer;
let x=value integer 123;
<print <x>>;
Float is another built-in type in Chomik. We can declare a simple variable of this type as follows:
variable x:float;
We can also declare multiple variables separating them with commas:
variable x:float, y:float, z:float;
The float literals are the same old floating-point numbers we know from the other languages. We use the decimal point, not the comma!
variable x:float;
let x=value float 3.14159;
Chomik supports also the mathematical notation:
variable x:float;
let x=value float 1.267e-10;
In order to print a float variable value we must again use the operator <>.
variable x:float;
let x=value float 1.267e-10;
<print <x>>;
We can also declare variables of the built-in type string:
variable x:string;
We can as well declare multiple variables separating them with a comma:
variable x:string, y:string, z:string;
We can also declare variables of the built-in type string:
variable x:string;
let x=value string "alpha";
When we want to copy another string variable value to our variable we can do it as follows:
variable x:string, y:string;
let x=value string "alpha";
let y=<x>;
In order to print a string variable value we must again use the operator <>.
variable x:string;
let x=value string "beta";
<print <x>>;
The time has come to explain the mystery of the <> operator. Why it has been introduced in Chomik?
variable something with a name consisting of multiple identifiers:integer;
let something with a name consisting of multiple identifiers=value integer 12345;
<print <something with a name consisting of multiple identifiers>>;
Now we see the reason why the <> operator is necessary. Without it the Chomik interpreter could not be sure which variable we mean. But what is it good for, to have variables with such unusual names? Before we explain this let us reveal one more secret.
Identifiers are just names. Nothing more. But the literals are also names. We often overlook this fact. For most us 10 is the number ten. But in fact it is just a name of the number ten in the decimal system. Moreover, it's meaning is different in other systems. It is, for instance, a name of the number 2 in the binary system! If literals are just names why not allow using them as variable names? Chomik allows that.
variable something with a name consisting of multiple identifiers and literals 1 "hallo" 3.14159 5: integer;
let something with a name consisting of multiple identifiers and literals 1 "hallo" 3.14159 5=value integer 12345;
<print <something with a name consisting of multiple identifiers and literals 1 "hallo" 3.14159 5>>;
We can even freely mix the literals with the identifiers, or even have no identifiers at all!
variable 1 "hallo" 3.14159 5:integer;
let 1 "hallo" 3.14159 5=value integer 12345;
<print <1 "hallo" 3.14159 5>>;
This bizarre code will not print 1 "hallo" 3.14159 5, but 12345, because this is the value of the variable 1 "hallo" 3.14159 5.
Isn't it dangerous? A code that does something else that we would expect? It might be. But there is a huge benefit of it: we can use values of some variables to assign other variables. Just like pointers. But without all the burden of the pointers arithmetic. We can simply write:
variable "alpha":integer, "beta":float, gamma:string;
let "alpha"=value integer 12345;
let "beta"=value float 3.14159;
let gamma=value string "alpha";
<print <<gamma>>>;
let gamma=value string "beta";
<print <<gamma>>>;
The above snippet will print out:
12345 3.14159
There are some more good news from Chomik: We do not have functions or procedures with the parameters passed on the stack at all. Not in Chomik. Instead there is another built-in type - code. Yes, you can declare variables of type code. And yes, you can execute them.
variable x: code;
As usual, you may have multiple such variables in one declaration.
variable x: code, y:code, z:code;
variable x: code;
let x=value code
{
<print "Hi">;
};
Note that the assignment must end with a semicolon, since it is a regular assignment. Code is just one more type. You could assign variable with some code, execute it, then assign it with a different code, and again execute it. The same variable. We do not need pointers to functions!
variable x: code;
let x=value code
{
<print "Hi">;
};
<x>;
We could as well execute a code literal, directly, without assigning it to a variable. This is less commonly used in Chomik, but it is possible.
value code
{
<print "Hi">;
};
or, more explicitly:
execute value code
{
<print "Hi">;
};
Note that within a code literal you can have assignments or executions, or even declarations. No problem. You only have to remember that Chomik does not support local variables. All the variables are global.
Probably you have already figured out that our '<print "hello world">;' is just a name of a variable. Print just happens to be implemented in C++ and has access to the Chomik internal data, something like a reflection. It has nothing to do with a stack or function parameters. In Chomik there is no stack.
The type "boolean" is one more built-in type in Chomik. There are just two values, false and true, but no operators, no expressions. Nothing. You can declare variables of type boolean and use them.
variable x:boolean;
You can assign your boolean variable with the boolean values (or values of other boolean variables):
variable x:boolean;
let x=value boolean false;
<print <x>>;
let x=value boolean true;
<print <x>>;
We will now learn a pattern that is very common in Chomik.
variable do something on true:code, do something on false: code;
let do something on true=value code
{
<print "it is TRUE!">;
};
let do something on false=value code
{
<print "it is FALSE!">;
};
#
# ....
#
variable our condition:boolean;
let our condition=value boolean true;
<do something on <our condition>>;
let our condition=value boolean false;
<do something on <our condition>>;
It will print out:
it is TRUE! it is FALSE!But we call <do something on <our condition>> twice, only changing the 'our condition' variable value! Yes. You are right. There is no conditional instruction in Chomik.
We will talk now about the placeholders. Placeholders are like handy small variables that you provide with the information of their type and let Chomik to execute your code for every possible value of them. Let us use first a very simple case - the above example will be written in a more efficient way:
variable do something on (X:boolean):code;
let do something on true=value code
{
<print "it is TRUE!">;
};
let do something on false=value code
{
<print "it is FALSE!">;
};
#
# ....
#
variable our condition:boolean;
let our condition=value boolean true;
<do something on <our condition>>;
let our condition=value boolean false;
<do something on <our condition>>;
It is our example for a conditional instruction equivalent in Chomik, but the first line is different. It contains a placeholder X of type boolean. The code does exactly the same, but it does not require you to declare the two variables, one for 'false' and one for 'true'. Is it any good for us? It is. The placeholder are a powerful tool in Chomik. Take a look at the following snippet:
variable do something on (X:boolean) and (Y:boolean) and (Z:boolean):code;
let do something on (X:boolean) and (Y:boolean) and (Z:boolean)=value code
{
<print "something is FALSE!">;
};
let do something on true and true and true=value code
{
<print "all are TRUE!">;
};
#
# ....
#
variable a:boolean, b:boolean, c:boolean;
let a=value boolean true;
let b=value boolean false;
let c=value boolean true;
<do something on <a> and <b> and <c>>;
let b=value boolean true;
<do something on <a> and <b> and <c>>;
The above snippet will print out:
something is FALSE! all are TRUE!What happened? We used the placeholders X,Y and Z, all of type boolean, not only in a declaration but also in the assignment. Chomik created all possible tuples, i.e.:
false false false false false true false true false false true true true false false true false true true true false true true trueIn other words it created a cartesian product of these sets (boolean x boolean x boolean). Then the second assignment simply overwrites the code 'do something on true and true and true'. Still not impressed? Take a look at this:
variable some function on (X:boolean) and (Y:boolean) and (Z:boolean):boolean;
let some function on (X:boolean) and (Y:boolean) and (Z:boolean)=value boolean false;
let some function on true and true and true=value boolean true;
<print (X:boolean) "and" (Y:boolean) "and" (Z:boolean) "=" <some function on (X:boolean) and (Y:boolean) and (Z:boolean)>>;
It will print out:
false and false and false = false true and false and false = false false and true and false = false true and true and false = false false and false and true = false true and false and true = false false and true and true = false true and true and true = trueThe point is that in Chomik you can use the placeholders to perform implicit loops in the declarations, in the assignments and also in the execution. Everywhere. And yes, there are no loop instructions in Chomik. Not a single one.
In Chomik there are no special functions like fprintf or sprintf. Instead we try to pack all the functionality in a single built-in family of variables, in this case "print". There are 3 built-in streams:
let variable the print target stream index=value integer 1;
<print "message">;
Note that all the following messages will be printed to the standard error stream, until you change 'the print target stream index'.
By default the 'print' family of codes variables put a single whitespace separator between the subsequent items that are to be printed. You can change the separator setting the 'the print separator' built-in string variable to a different value:
let the print separator=value string "";
<print "alpha" "beta" "gamma">;
The above snippet will print "alphabetagamma".
By default the 'print' codes will add the end of line string after each print. You can change this end of line string by setting 'the print end of line' built-in string variable:
let the print end of line=value string "";
<print "alpha">;
let the print end of line=value string "\n";
<print "beta">;
<print "gamma">;
The above snippet will print out "alpha beta " and in the next line "gamma ", because we have set in the third line 'the print end of line' value to string "\n".
There is a built-in family of codes 'create'. It can be used to create a stringstream. After the creation the index to be used with for example 'print' codes is stored in the 'the created stream index' built-in integer variable. You may want to store it in your variable for later use:
<create new output stringstream>;
let x=<the created stream index>;
If you set for example 'the print target stream index' to the value stored in 'x' (from the above example) then all 'print' codes will print to that stream.
There is a built-in family of variables 'get from stream'. It is intended for the stringstreams (it returns the whole string as a result). The stream index used by these codes is stored in the built-in integer variable 'the get from stream stream index'. The code 'get from stream' will use the stream to get the whole string and will store it in the built-in string variable 'the get from stream result'.
<create new output stringstream>;
let x=<the created stream index>;
#
# now let us tell the 'print' to write to this stream
#
let the print target stream index=<x>;
let the print end of line = value string "";
<print "something" 1 2 3>;
let the print target stream index=value integer 0; # back to the standard output
let the print end of line = value string "\n";
#
# now let us tell the 'get from stream' to get from it:
let the get from stream stream index=<x>;
<get from stream>; # getting the result from the stream
<print <the get from stream result>>;
Very often you will want to reuse the once created stringstream (since they are global!). How to set the stringstream content? For example with the empty string? You can use the 'set to stream ""' built-in code. It uses the stream indicated by the 'the set to stream stream index' built-in variable.
<create new output stringstream>;
let x=<the created stream index>;
#
# now let us tell the 'print' to write to this stream
#
let the print target stream index=<x>;
let the print end of line = value string "";
<print "something" 1 2 3>;
#
# let us first set the stream content to "":
let the set to stream stream index=<x>;
<set to stream "">;
#
# now let us print something else
#
<print "something else" 4 5 6>;
let the print target stream index=value integer 0; # back to the standard output
let the print end of line = value string "\n";
#
# now let us tell the 'get from stream' to get from it:
let the get from stream stream index=<x>;
<get from stream>; # getting the result from the stream
<print <the get from stream result>>;
The above example first prints 'something 1 2 3 ' to the stringstream, then clears it with 'set to stream ""', then prints 'something else 4 5 6'. The result obtained by the 'get from stream' equals only the latter ('something else 4 5 6').
Both for the input stream and for the stringstreams we can use the 'read from stream "string"' built-in code to read a single string. We set the stream index by assigning the built-in integer variable 'the read from stream source stream index'. The result will be stored in the built-in string variable 'the read from stream result "string"'.
<create new output stringstream>;
let x=<the created stream index>;
#
# now let us tell the 'print' to write to this stream
#
let the print target stream index=<x>;
let the print end of line = value string "";
<print "something" 1 2 3>;
let the print target stream index=value integer 0; # back to the standard output
let the print end of line = value string "\n";
#
# now let us tell the 'read from stream "string"' to read from it:
let the read from stream source stream index=<x>;
<read from stream "string">;
<print <the read from stream result "string">>;
The above snippet will print out only "something", because this is the first word from the content of our stringstream.
Chomik supports the UTF-8 encoding.
<create new output stringstream>;
let x=<the created stream index>;
#
# now let us tell the 'print' to write to this stream
#
let the print target stream index=<x>;
let the print end of line = value string "";
<print "今日は">;
let the print target stream index=value integer 0; # back to the standard output
let the print end of line = value string "\n";
#
let the read from stream max size=value integer 1; # we read 1 by 1 (UTF-8 characters!)
# now let us tell the 'read from stream "string"' to read from it:
let the read from stream source stream index=<x>;
<read from stream "string">;
<print <the read from stream result "string">>;
The above snippet will store the text "今日は" in a stringstream, then it will read 1 UTF-8 character from it. The result will be "今". Quite correct. We achieve this effect by setting the value of the built-in integer variable 'the read from stream max size'. The size is expressed in characters, not in bytes. The default value of the max size is 0, which means we are reading until we encounter a space character.
Both for the input stream and for the stringstreams we can use the 'read from stream "integer"' built-in code to read a single integer. We set the stream index by assigning the built-in integer variable 'the read from stream source stream index'. The result will be stored in the built-in integer variable 'the read from stream result "integer"'.
<create new output stringstream>;
let x=<the created stream index>;
#
# now let us tell the 'print' to write to this stream
#
let the print target stream index=<x>;
let the print end of line = value string "";
<print 123 234 345>;
let the print target stream index=value integer 0; # back to the standard output
let the print end of line = value string "\n";
#
# now let us tell the 'read from stream "integer"' to read from it:
let the read from stream source stream index=<x>;
<read from stream "integer">;
<print <the read from stream result "integer">>;
The above snippet will print out 123.
There is a special 'type' command to declare your enumerations. In order this to work we need an additional command - 'expand'. It takes one integer literal as a parameter (no variables are allowed). This literal should be 1 unless you use more complex features of the enumerations.
type person={Gotrek, Gerrudir, Gwaigilion};
expand(1);
type person={Gotrek, Gerrudir, Gwaigilion};
expand(1);
<print (P:person)>;
You may use the enumerations as types. In fact, the boolean built-in type is an enumeration.
type person={Gotrek, Gerrudir, Gwaigilion};
expand(1);
variable x:person;
You can use the common way to assign your enumeration variable with a literal.
type person={Gotrek, Gerrudir, Gwaigilion};
expand(1);
variable x:person;
let x=value person Gwaigilion;
<print <x>>;
type person={Gotrek, Gerrudir, Gwaigilion}, place={Krakow, Warszawa, Wroclaw, Poznan, Gdansk};
type information={(X:person) likes (Y:place)};
expand(2);
<print (I:information)>;
We can declare the enumerations depending on other enumerations. In the above snippet we use the placeholders to declare an 'information' enumeration. The parameter of the 'expand' special command is the maximum depth of these dependencies. The above snippet will print out all possible informations for this example.
GotreklikesKrakow GerrudirlikesKrakow GwaigilionlikesKrakow GotreklikesWarszawa GerrudirlikesWarszawa GwaigilionlikesWarszawa GotreklikesWroclaw GerrudirlikesWroclaw GwaigilionlikesWroclaw GotreklikesPoznan GerrudirlikesPoznan GwaigilionlikesPoznan GotreklikesGdansk GerrudirlikesGdansk GwaigilionlikesGdansk
The types used in the placeholders used to declare your enumerations can be the enumeration itself. Chomik allows therefore the recursive enumerations!
type person={Gotrek, Gerrudir, Gwaigilion}, place={Krakow, Warszawa, Wroclaw, Poznan, Gdansk};
type information={(X:person) likes (Y:place), (X:person) has told (Y:person) that (I:information)};
expand(3);
<print (I:information)>;
If we expand it with the parameter 3, then we will obtain for instance 'GotrekhastoldGwaigilionthatGotreklikesKrakow'.
The types used in the placeholders used to declare your enumerations can be even the enumerations that have not yet been defined! Remember, we need an appropriate value of the expand command parameter to really create the enumerations.
type person={Gotrek, Gerrudir, Gwaigilion}, place={Krakow, Warszawa, Wroclaw, Poznan, Gdansk};
type information={(X:person) likes (Y:place),
(X:person) has told (Y:person) that (I:information),
(X:person) has asked (Y:person) to (Z:action)};
type action={go to (X:place), tell (X:person) that (Y:information)};
expand(4);
<print (I:information)>;
In the above snippet one of the informations is depending on action. And the action type has not yet been defined! Also in the action declaration we have a dependency on the information type.
There is also a built-in type compare_result. You can use the usual method to print out all its values:
<print (C:compare_result)>;
The values are 'lower', 'equal' and 'greater'. In order to compare for example two strings we need to use the built-in code 'compare "string" "a" "a"'. It will compare them lexicographically and store the result in the built-in variable 'the compare result'.
<compare "string" "alpha" "alpha">;
<print <the compare result>>;
The above snippet will print out 'equal'.
You can use the 'compare' built-in codes with the types that you have defined:
type person={Gotrek, Gerrudir, Gwaigilion};
expand(1);
variable some person: person;
let some person=value person Gwaigilion;
<compare "person" <some person> Gerrudir>;
<print <the compare result>>;
Remember to pass the type name in quotation marks (it must be 'compare "person" .. ..', 'compare person' will not work!). Comparing autmatically "learns" abut the new types you have declared and compares them like strings.
In order to compare two integers you need to use the 'compare "integer" .. ..' built-in codes:
variable x:integer;
let x=value integer 123;
<compare "integer" <x> 1000>;
<print <the compare result>>;
By now you may have already figured out how to execute some code depending on whether x is lower or greater or equal 1000:
variable do something on (X:compare_result):code;
let do something on lower=value code
{
<print "it is lower!">;
};
let do something on equal=value code
{
<print "it is equal!">;
};
let do something on greater=value code
{
<print "it is greater!">;
};
#
# ....
#
variable x:integer;
let x=value integer 100;
<compare "integer" <x> 1000>;
<do something on <the compare result>>;
let x=value integer 10000;
<compare "integer" <x> 1000>;
<do something on <the compare result>>;
In order to add two integers you can use the built-in 'add "integer" .. ..' codes. The result is also an integer and is stored in the built-in variable 'the add result "integer"'.
variable x:integer;
let x=value integer 100;
<add "integer" <x> 1>;
<print <the add result "integer">>;
In order to subtract two integers you can use the built-in 'subtract "integer" .. ..' codes. The result is also an integer and is stored in the built-in variable 'the subtract result "integer"'.
variable x:integer;
let x=value integer 100;
<subtract "integer" <x> 1>;
<print <the subtract result "integer">>;
In order to multiply two integers you can use the built-in 'multiply "integer" .. ..' codes. The result is also an integer and is stored in the built-in variable 'the multiply result "integer"'.
variable x:integer;
let x=value integer 100;
<multiply "integer" <x> <x>>;
<print <the multiply result "integer">>;
In order to divide two integers you can use the built-in 'divide "float" .. ..' codes. The result is a float and is stored in the built-in variable 'the divide result "float"'.
variable x:integer;
let x=value integer 100;
<divide "float" <x> 3>;
<print <the divide result "float">>;
In Chomik there is a way to check whether a variable (or all variables in a family) have been defined in memory. First we need to assign the value of a a predefined boolean variable "the get is defined result" to true. Then we call "get is defined ..." where "..." is replaced by the name of the variable that we want to check. We are free to use placeholders there. Then we can check the value of the variable "the get is defined result". It will be true if (and only if) all the variables from the family are defined.
let the get is defined result = value boolean true;
<get is defined Gotrek action text 1>; # this is a new "get" predefined in chomik...
# it sets the boolean flag "the get is defined result" to false if Gotrek action text ...
# is not in memory
<print <the get is defined result>>;
There is a special built-in code 'get amount of variables in the memory'. After you call it you can check the value of the predefined integer variable "the get amount of variables in the memory result".
<get amount of variables in the memory>; # this is a new "get" built-in chomik.
<print <the get amount of variables in the memory result>>;
Each variable in the memory has a so called signature. A signature consists of items. For each variable in the memory there can be a different amount of the signature items. There is a special built-in code 'get amount of items in the memory variables signature' to get the amount for the variable at the given index (in the example below the variable at the index 0). After you call it you can check the value of the predefined integer variable "the get amount of items in the memory variables signature result".
<get amount of items in the memory variables signature 0>; # this is a new "get" predefined in chomik...
<print <the get amount of items in the memory variables signature result>>;
Each variable in the memory has a so called signature. A signature consists of items. For each variable in the memory there can be a different amount of the signature items. There is a special built-in code 'get signature item representation' to get the text representation for the variable at the given index, for the signature item at the given item index, (in the example below the variable at the variable index 0 and item index 0). After you call it you can check the value of the predefined string variable "the get signature item representation result".
<get signature item representation 0 0>; # this is a new "get" predefined in chomik...
<print <the get signature item representation result>>;
There is a family of codes "create new signature regular expressions ...". It requires a string (see the below example), creates a new "regular expression" and assigns the predefined integer "the created signature regular expression index" to its index. In order to use it there is a family of codes named "match ...". It assigns the predefined boolean variable "the match result" and (if matched) the predefined integer variables 'the match group "integer" 1', 'the match group "integer" 2' and so on.
<create new signature regular expression "person (X:integer) knows person (Y:integer)">;
let my signature regular expression index = <the created signature regular expression index>;
let the match expression index = <my signature regular expression index>;
<match person2knowsperson3>; # for the signature 'person 2 knows person 3', the result should be true
<print <the match result>>;
<print <the match group "integer" 1>>; # the result should be 2
<print <the match group "integer" 2>>; # the result should be 3
In the family of codes "create new signature regular expressions ..." it is possible to use (apart from "integer") also the "boolean" type. . It requires a string (see the below example), creates a new "regular expression" and assigns the predefined integer "the created signature regular expression index" to its index. In order to use it there is a family of codes named "match ...". It assigns the predefined boolean variable "the match result" and (if matched) the predefined integer variables 'the match group "integer" 1', 'the match group "integer" 2' and so on. For the boolean results it sets 'the match group "boolean" 1', 'the match group "boolean" 2' and so on. You can of course mix the integer results with the boolean ones.
<create new signature regular expression "person (X:integer) likes person (Y:integer) is (B:boolean)">;
let my signature regular expression index = <the created signature regular expression index>;
let the match expression index = <my signature regular expression index>;
<match person2likesperson3isfalse>; # for the signature 'person 2 likes person 3 is false', the result should be true
<print <the match result>>;
<print <the match group "boolean" 3>>;