k7 is a programming language.
- stack-based
- self-hosted (k7 source code is written in k7)
- compiles down to assembly (x86_64)
- concatenative
- inspired by Porth
- experimental++
- unfinished and never will
- pronounced /kas.εt/
- statically typed
- not for the faint of heart
- Linux only
- Turing-complete
func add ( x int , y int )
x y +
end
var result int in
@result 2 2 add :=
if result 5 = then
"Hello, dystopian World!\n" print
end
Install fasm to automatically compile the assembly code k7 generates, or to bootstrap the k7 compiler.
fasm executable must be located here /usr/bin/fasm.
$ ./k7 -run ./examples/hello.k7
Hello, World!Bootstrap the compiler with fasm, then give the compiler its own source code to get... the exact same compiler!
$ fasm -m 324017 ./src/k7.asm
$ ./src/k7 -v ./src/k7.k7 -o ./k7Documentation below is brief and non-exhaustive.
I'd recommend checking ./examples/ programs to get a grasp on how to write k7.
# this is a comment
k7 is stack-based. You push stuff and operate on a stack.
10 20 +
We just pushed two integers (10 and 20) and one operator (+) on the stack. What happened:
10 # push 10 (stack = 10)
20 # push 20 (stack = 10 20)
+ # act on stack (stack = 30)
We have a plethora of operators. Most of them are self-explanatory.
+ - * / mod # arithmetic
>> << # bitwise
= != > >= < <= # comparison
and or not # logic
The following are "stolen" from Forth. Check out their webpage for a better explanation:
dup # duplicate last value on the stack
2dup # duplicate last pair
swap # swap last two values
2swap # swap last two pairs
lob # duplicate penultimate value ("over" in Forth)
2lob # duplicate penultimate pair ("2over" in Forth)
rot # move antepenultimate value to last position
drop # remove last value
- Keywords:
print,put
Use
Use
putto print numeric values
"Hello world\n" print
10 20 + put # prints 30
print requires two values on the stack: length and address (that's what strings are in k7).
put requires one numeric value on the stack.
- Structure:
"anything between non-escaped double-quotes"
Strings push two things on the stack: their length and address
Strings can be multiline
"Foo" # push 3 (length) and string address on stack
"Bar\n" # push 4 and address on stack
"here
is a
multiline
string"
- Structure:
'_'
Characters litterals push their ASCII code on the stack
'a' put # prints 97
'\n' put # prints 10
- Keywords:
true,false
False = 0
True = 1
- Keywords:
if,elif,else,end - Structure:
if <condition> then <instructions> [elif <condition> then <instructions> [else <instructions>]] end
if weather sunny = then
"sunglasses" print
elif weather rain = then
"umbrella" print
elif weather hail = then
"helmet" print
end
- Keywords:
for,while,do,done - Structure:
- for loop:
<iterations> for <instructions> 1 - done - while loop:
while <condition> do <instructions> done
- for loop:
For loops push the iterator on the stack and stop when it is equal to zero
While loops continue until their condition is false
# print from 10 to 1 (descending)
10 for
dup # duplicate iterator
put # and prints it
1 - # decrease iterator by 1
done
drop # remove iterator from stack
# print from 1 to 10
1 while dup 10 <= do
dup put
1 +
done drop
- Keywords:
continue,break
Built-in types and their respective sizes (in bytes):
u8(1)u16(2)u32(4)u64(8)int(8)ptr(8)char(1)bool(1)
- Keywords:
def,end - Structure:
def <identifier> <instructions> end
Literally rewrite every occurrence of
<identifier>to<instructions>at compile time.Macros are scoped (i.e. they are either global, either local when declared inside functions).
def N 3 end
N N * # will be rewritten as 3 3 *
- Keywords:
func,end - Structure:
func <identifier> [ parameters ] <body> end - Parameters:
( <var0_identifier> <var0_type> [, <var1_identifier> <var1_type> , ...] )
Parameters are optional.
Functions are scoped.
Functions can be nested.
func fib ( n int )
if n 2 < then
1 return
end
n 1 - fib n 2 - fib +
end
10 fib # call fib with n = 10
put # outputs => 89
- Keywords:
var,in - Structure:
- declare variable:
var <identifier> <type> in - assign to variable:
@<identifier> <value> := - reverse assign:
<value> @<identifier> =:
- declare variable:
var age u8 in
@age 36 := # assign 36 to variable "age"
var name char ptr in
"Luc" swap drop @name =: # assign (reverse) string address to "name"
To refer to the value of a variable, simply call it with its identifier.
If you want to refer to the address of a variable (when assigning to it, or passing it as a pointer) prepend a @ to its identifier.
- Keywords:
type,end - Structure:
type <identifier> <type> end
type age u8 end
type input char ptr end
- Keywords:
struct,field,end - Structure:
struct <identifier> [field <field_id> <field_type> end]+ end
struct person
field name char ptr end
field age u8 end
end
var john person in
@john.name "John" swap drop :=
@john.age 99 :=
We mimick C struct memory alignment, thus receiving structured data from kernel is easy (check ./lib/ctypes.k7).
Structs are syntactic sugar, in the background they are just variables (and allocations for padding). If structs are passed by reference to a function, there is no easy way to access fields other than using memory load/store operators manually.
- Keywords:
alloc,end - Structure:
alloc <identifier> <size> end
Allocate a chunk of size bytes.
Allocations are scoped.
Size can be simple arithmetic operations on types
alloc my_str char 256 * end # allocate a chunk of 256 bytes
255 for
dup my_str + lob >(8) # store byte X at index X of my_str
done drop
- Keyword:
import - Structure:
import "file_path"
Import other k7 programs into the current one.
If no absolute path given, will look in the relative ./lib directory.
import "./lib/maths.k7" # or simply
import "maths" # same as above
Check ./lib for available libraries.
# loading operators
m> # load as a 64-bits object
(64)> # load as a 64-bits object
(32)> # load as a 32-bits object
(16)> # load as a 16-bits object
(8)> # load as a 8-bits object
# storing operators
>m # store as a 64-bits object
>(64) # store as a 64-bits object
>(32) # store as a 32-bits object
>(16) # store as a 16-bits object
>(8) # store as a 8-bits object
var x int in @x 100 :=
var p int ptr in @p @x :=
p m> put # prints 100
p 50 >m # stores 50 in @x as 64-bits integer
x put # prints 50
- Keywords:
sys0,sys1,sys2,sys3,sys4,sys5,sys6 - Structure:
[parameters] syscall
60 1 sys1 # exit 1 syscall
enum
BLACK # 0
BLUE # 1
GREEN # 2
YELLOW # 3
end
- useful logs/error messages (worst feature to lack..)
- floats
- negative litterals
- compiler optimization
- variables direct value assignement
- string interpolation
- everything else
- type checker (too flexible for now)
- structs (no easy fields access when passed by reference)
- dereferencing
- everything else
Sorry non-Vim users, I only came up with k7.vim.
$ cp ./syntax/k7.vim ~/.config/vim/syntax/k7.vim
$ echo 'autocmd BufNewFile,BufRead *.k7 set filetype=k7 syntax=k7' >> ~/.vimrck7 is the result of a two-months limit experiment. Experiment motivated by the idea of generating assembly code from own higher lever instructions. Idea instilled by Porth developer in their video series.
Not only being the first programming language I've written, k7 is also the first stack-based programming language I've ever used. Just for the records. It was a fun project.
[o=o]