Zekai is a toy programming language for hobbyists and enthusiasts.
Its simple source code (150 lines) and modern syntax can serve as a basis for building new programming languages.
-
Download zekai.zip, extract it and open the file zekai-dev.html.
Or Try it online.
-
Type in the following zekai code:
fib(n):
n == 0? 0
n == 1? 1
fib(n-1) + fib(n-2)
main() {
i = 2 + 2
alert('your lucky number is:' + fib(i))
}
- Press F8 or Shift+Enter to run.
Next code is more complex:
main():
has(g i j): i >= 0 & i < size(g) & j >= 0 & j < size(g[i])? g[i][j] 0
add(a b): a + b
grid = to(40).map(\:to(40).map(\:rand(2)))
io(canvas(200 200) \in out:
grid <- grid.map(\row i: row.map(\c j {
n = range(~1 2).map(\a: range(~1 2).map(\b:
!a & !b? 0 has(grid i+a j+b)
).reduce(add)).reduce(add)
v = n == 2? c n == 3? 1 0
fill(out v? '#fd0' 'black' j*5 i*5 5 5)
v
}))
)
#
# Utility functions
#
size (v): v.length
rand (n): Math.floor(Math.random() * n)
range (i n): n >= 0? Array(n-i).join(' ').split(' ').map(\e v: v+i) []
to (n): range(0 n)
#
# Output functions
#
canvas(w h) {
c = document.createElement('canvas')
c.width <- w
c.height <- h
c
}
io(out main) {
animate() {
main({} out)
window.requestAnimationFrame(animate)
}
animate()
document.body.appendChild(out)
}
fill(out c x=0 y=0 w=out.width h=out.height a=1 blend='') {
q = out.getContext('2d')
q.save()
q.globalAlpha <- a
q.globalCompositeOperation <- blend
q.fillStyle <- c
q.fillRect(x y w h)
q.restore()
}
Basics:
-
Whitespace is not significative
-
Identifiers matches the regex
[\\$A-Za-z_][\\$A-Za-z_\\d]*
-
Line comments starts with
#
Literal Example ECMAScript Number 7
7.77
0xAF
(same) String 'awesome'
(same) Array [1 2 3]
[1,2,3]
Object { x=1 y=2 }
{ x:1, y:2 }
Function \a b c: 0
function(a,b,c) { return 0; }
-
A program is a set of definitions
-
A definition is an identifier and an expression, written:
id = expr
Optionally, the definition of a function expression can be written as:
id = \a b c: expr
id(a b c): expr # better
- The body of a function can be either a single expression, or a block of expressions.
Using
:
and{}
respectively:
foo(a b): a + b
bar(a b) {
a()
b()
}
-
Commas and semi-colons are not necessary, but can be used to improve readability
-
Let expressions are a definition and an expression (
id = expr expr
). Example:
main():
a = 2 + 2
print(a)
Let expressions in blocks are also valid:
main() {
a = 2 + 2
print(a)
}
- A struct is syntactic sugar for a function that returns an object with the arguments as fields
foo(a b): { a=a b=b }
bar(a b)@
foo = \i=0 j=0 @
- Conditional expressions
cond? true false
. Example:
main() {
print(1? 2 3) # 2
print(0? 2 3) # 3
}
- Negative numbers are written with operator
~
, example:
abs(a): a < 0? ~a a
- Updates uses the symbol
<-
. (see Notes)
main() {
s = ''
s <- prompt()
}
# ECMAScript
function main() {
var s = '';
s = prompt();
};
-
Finally, operators that behave like ECMAScript:
Operator Symbol Arithmetic + - * / %
Relational == != >= <= > <
Not !
Logical ``` Call ()
Index []
Field .
Refer to the source code grammar for details.
Zekai project is composed of the following files:
File name | Description |
---|---|
zekai | source code |
zekai.js | source code compiled to ECMAScript |
zekai-dev.html | editor |
LICENSE | MIT |
README.md | this file |
-
Zekai was created as a project for personal study. Becoming mature, it may serve as a basis for creating new programming languages.
-
Zekai should compile to WebAssembly, since it is not here yet, the best option is ECMAScript.
-
There is no need to introduce a new -standard- library. Better choose an existing library which fits the needed functionality.
-
Structs instead of classes emphasize there is no inheritance.
-
There are not keywords.
-
Both
null
andundefined
should never be referred, unless working with an ECMAScript library. -
There is no
new
idiom. To call ECMAScript library functions use:
new(class args=[]) {
obj = { __proto__ = class.prototype }
class.apply(obj args)
obj
}
-
Use a strategy to encapsulate side-effects (for example; forbid them outside of main).
-
Code should be written to be statically type inferred.
-
Static check analysis can be done by formatting the compiled ECMAScript and then using Flow.
-
The implementation of the compiler translates Zekai code to ECMAScript.
(Zekai works on any ECMAScript environment)
-
A raw way to run Zekai code on a browser environment without using the editor:
<html>
<head>
<script src="zekai.js"></script>
<script>
eval(zekai.program('main(): alert(0)')).main()
</script>
</head>
<body>
</body>
</html>
- To run Zekai code in a Node environment:
// ECMAScript
var read = function(path, onload) { return require('fs').readFile(path, onload); };
read('zekai.js', function(err, data) {
eval(data);
read(arguments[2], function(err, data) {
eval(zekai.program(data));
});
});
-
Another option is to save the compiled output, and load it as regular ECMAScript.
-
The output of compiling the file zekai is the content of zekai.js.
-
To get the compiled ECMAScript run:
main():
console.log(zekai.program(code))
-
Pure code is portable on virtual machines.
-
To write portable impure code, a layer/library is needed.
-
It is not possible to know beforehand what kind of IO actions the software needs.
-
Source files should start with
zekai`
and end with`
to bypass the browser's local file access restriction.
Zekai is MIT-licensed.