KimL
KimL is an esoteric programming language consisting of invocations on pre-defined pseudo-objects. KimL was created by User:Alexanderdna, who created other languages such as ILYC and TS#.
The current implementation of KimL is written in C++ as both a compiler and a virtual machine that executes the compiled bytecode.
KimL is a case-sensitive language.
History
KimL was firstly developed and implemented in 2009. As the project was started in December of 2009, the first language specification is Specification 09.12 and the interpreter was written using Visual Basic .NET.
In the later year, another version called Specification 10.07 was implemented in Visual C# in July 2010.
The third version of KimL, born in January 2011 and called Specification 11.01, is the last version till now and is described in this document.
Structure
There are three data types in KimL, which are int, real and string. int is 32-bit integer. real is 64-bit floating-point number. string is array of 8-bit character or the std::string type in the C++ Standard Template Library.
Values of type int can be converted to type real or string. Values of type real can be converted to type int or string. Values of type string generally cannot be converted to int or real. However, in some situations, string to numeric conversions are allowed and the runtime will try to parse the string for a valid value or choose 0 if it fails.
A KimL program has an internal stack call the k-stack. This is a supposedly-unbound stack that can store objects of any type.
Besides, there is an array of 128 objects called the tape. The tape also accepts any of the three data types. A tape pointer exists to point to the current item on the tape.
Objects
There are pseudo-objects taking care of different tasks in the program. Each object has a set of methods corresponding to the works it represents. Some methods have arguments, some do not.
io
As the name suggests, io is the object performing input and output tasks. It has two methods, which are in and out.
io.in
This method takes data from a text line the user has entered.
One version of io.in uses a type keyword as argument. It then converts the input data to the specified type and pushes it to the k-stack.
io.in int io.in real io.in string
Another version uses a variable name as argument. The input data is converted to the variable’s type and stored in that variable.
io.in a ;where 'a' is a variable
Note: if the type conversion fails, the default value is used.
io.out
This method uses an expression a argument, converts the expression result to string (if needed) and prints that on the screen.
io.out "Hello World!"
var
The var object is used to work on variables.
var.decl
This method helps declaring a variable for use in the program. It uses a type keyword and a variable name as arguments. The variable should not be declared before unless it has been deleted by var.del (discussed later).
var.decl int n var.decl real r var.decl string s
The variable can have an initial value as showed below:
var.decl int n = 10 var.decl string s = "a simple stupid string" var.decl real r = n * 3.14
The initial value's type should be convertible to the variable type, otherwise an error will occur in compile time or runtime depending on the nature of the expression.
var.del
This method deletes an existing variable. If the variable is not declared before, a compile error will occur.
var.del n ;suppose that 'n' exist
Variable declaration and deletion in a KimL program are checked sequentially at compile time. That means, a variable is considered to exist if a former line of code has declared it, and to no longer exist if a former line of code has deleted it. Control flows are not included in the check.
Due to this characteristic of the compiler, if the program, in runtime, jumps to a position where a variable has not been declared and references or deletes it, an error or undefined behaviour will occur.
Programmers are recommended not to have a branching between the declaration and deletion of a variable.
var.set
This method assigns a value to a variable.
var.set i = i + 1
A compile error will occur if the variable does not exist.
stack
The stack object have methods to operate on the k-stack.
stack.push
This method pushes a value to the k-stack.
stack.push n + r / 2
stack.pop
This method pops the top value off the k-stack. If the stack is empty, a runtime error will occur.
stack.pop
If given a variable as argument, this method stores the popped value in the variable.
stack.pop n ;n should exist
stack.peek
This method stores the top value on the stack in the given variable. A runtime error will occur if the stack is empty.
stack.peek n ;n should exist
stack.swap
This method swaps the top two values on the stack. A runtime error will occur if the stack has less than two values.
stack.swap
stack.clear
This method clears the whole stack.
stack.clear
If given a constant integer number as argument, this method clears that number of values on the stack. A runtime error will occur if the stack has less values than specified.
stack.clear 5
tape
The tape object operates on the program tape and tape pointer.
tape.read
This method reads the current value on the tape, pointed to by the tape pointer. The read value is then stored in a variable or pushed on the k-stack. Like io.in, the argument is a variable or a type keyword (for pushing on the stack).
tape.read a ;a should exist tape.read string
If the value cannot be converted to the specified type, a runtime error will occur.
If an 'at index' phrase is provided, the value at that index will be read. The index should be a constant integer number in the range from 0 through 127.
tape.read int at 3 tape.read n at 127
tape.write
This method writes the given value to the current object on the tape, pointed to by the tape pointer. If an index is provided, the value is written to the object at that position.
tape.write 10 ;write to current tape.write a * b + c at 0
tape.next
This method moves the tape pointer one position forward. If the tape pointer is currently 127, it will be set to 0.
tape.next
tape.prev
This method moves the tape pointer one position backward. If the tape pointer is currently 0, it will be set to 127.
tape.prev
tape.move
This method uses an expression as argument. The value of the expression will be converted to integer if needed and assigned to the tape pointer.
If the type conversion fails or the value is out of range (0 to 127), a runtime error will occur.
tape.move i + 3 * j
ctrl
The ctrl object provides control flow operations.
ctrl.goto
This method move the program counter to a defined label.
ctrl.goto L1 ;... L1: ;...
If a condition phrase is provided, the branching is done only if the specified condition is met.
ctrl.goto L1 if a > 0
ctrl.call
This method works like ctrl.goto but it stores the current program counter in a call stack before branching.
ctrl.call proc1
A condition is also allowed.
ctrl.call proc1 if name = "john" and age > 15
ctrl.return
This method pops a value from the call stack and sets it to the current program counter. If the call stack is empty, it will set 0 to the program counter.
ctrl.return
This method is, by convention, used with ctrl.call.
ctrl.end
This method halts the execution of the program.
ctrl.end
Expressions
Literals
Valid int literals are 0, 10, 300, 1000000, etc.
Valid real literals are 0.0, 1.23, 0.245, etc.
String literals are enclosed by double-quotes. Allowed escape sequences are \n, \r, \t, \\, \".
Operators
The following list are allowed operators in KimL, in descending order of priority.
- # converts to real, @ converts to int
- ^ exponentiation
- unary + and -, not (logic)
- multiplication, / division, \ integral division
- + addition, - subtraction
- & string concatenation
- < less than, <= less than or equal, > greater than, >= greater than or equal
- = equal, <> not equal
- xor (logic)
- and (logic)
- or (logic)
The # and @ conversion operators have a function syntax, that is, they require the converted expression be enclosed in parentheses.
Functions
KimL provides a set of built-in functions as listed below:
- abs(numeric): returns the absolute value of a number, the return type depends on the argument type.
- real sqrt(numeric): returns the square root of a number.
- real sin(numeric): returns the sine of an angle (in radians).
- real cos(numeric): returns the cosine of an angle.
- real tan(numeric): returns the tangent of an angle.
- real asin(numeric): returns the arc-sine of a number.
- real acos(numeric): returns the arc-cosine of a number.
- real atan(numeric): returns the arc-tangent of a number.
- string chr(numeric): returns a character from the given number(mod 256).
- int asc(string s): returns the ASCII value of the first character in the given string or 0 if the string is empty.
- int len(string s): returns the length of the given string.
- string left(string s, int n): returns the leading n characters of the given string, or the string itself if n is bigger than its length.
- string mid(string s, int i, int n): returns n characters of the given string, starting from i; or the string itself if i or i+n is bigger than its length.
- string right(string s, int n): returns the trailing n characters of the given string, or the string itself if n is bigger than its length.
- iif(cond, t, f): returns t if cond is true, otherwise f; the return type depends on the type of t or f.
- _tape(int idx): returns a value on the tape, pointed to by idx.
- _stack(int offset): returns a value on the stack, at the given offset from the top of the stack. _stack(1) returns the top value, _stack(2) returns the next to top value, and so on.
- _pop(): pops and returns the top value on the stack.
- _peek(): returns the top value on the stack.
Examples
Hello World
io.out "Hello World!\n"
Factorial
var.decl int f io.out "n = " io.in int ctrl.call fact var.set f = _tape(0) io.out "n! = " & f ctrl.end ; the following factorial function is recursive fact: ctrl.goto fact_0 if _peek() < 0 ctrl.goto fact_1 if _peek() <= 1 stack.push _peek() - 1 ctrl.call fact tape.write _pop() * _tape(0) at 0 ctrl.return fact_0: stack.pop tape.write 0 at 0 ctrl.return fact_1: stack.pop tape.write 1 at 0 ctrl.return
99 bottles of beer
var.decl int bot = 99 loop: io.out bot & " bottles of beer on the wall.\n" io.out bot & " bottles of beer.\n" var.set bot = bot - 1 ctrl.goto loop if bot > 0 io.out "No bottle of beer on the wall.\nNo bottle of beer.\n" io.out "Go to the store. Buy some more.\n" io.in string
The KimL calling convention
Below is the standard calling convention in KimL:
- Arguments are pushed on the stack, in right-to-left order.
- Return value is stored on the tape at 0.
- The callee is responsible for clearing the stack.
External resources
The KimL compiler/virtual machine is written in C++ and distributed under the MIT license. The source code is successfully compiled on Microsoft Windows XP and Ubuntu Linux using Microsoft Visual C++ 2008 Express and GCC 3.4.2.
Please visit https://www.sourceforge.net/projects/kiml to download the package.