Zoc is a block oriented, stack based concatenative programming language inspired by Zig, Forth and Porth.
The goal is to provide a feature-full language that can be self hosted and used for everyday programming (or not).
Zoc programs lives on the stack; no heap allocation except the ones you make.
Zoc have a strong typing that make errors harder to do.
- Compiled
- Stack based
- Concatenative
- Strongly typed
- No hidden allocations
- No hidden control flow
- Usable in production (or not)
- Lexer
- Parser
- ZIR generation
- Type analysis 🚧
- Semantic analysis
- AIR generation
- Code generation
- Basic arithmetic
- Basic type checking
@syscall- Basic functions
- Stack manipulation
- Basic if else-if else
- Full arithmetic, logic and boolean operators
- More type checking
- Basic while
- Basic const and var
- Arrays and slices
- Pointer: reference and dereference
- Strings, characters and multiline strings
- Even more type checking
- Lifetime and scope
defer
- Basic for
- Basic enum
- Basic struct
- Basic union
- Some type checking
- Value capturing
- Basic builtins
@import@as@intCast@compileLog@compileError@panic
The Zoc compiler is fully written in Zig (0.13). The source code for the compiler sits in src/.
The compiler generate FASM assembly, so you'll need to have it installed.
To build and run the Zoc compiler
zig build run -- <args>
Zoc has 3 stacks:
- data stack: your normal OS stack for all the data needed
- working stack: pointers to the data in the data stack
- return stack: pointers for returning from calls
The working and return stack are of fixed length.
Note that this is actually the goal of the project. It may change at any time.
Comments starts with // and ends on the new line.
10// 11 commented12+A string is a null-terminated slice of bytes (a pointer and a length).
Escaping sequences are supported:
\n- new line\r- carriage return\"- double quote\'- single quote\t- tab\xFF- hexadecimal byte\\- back slash
"Hello, world!\n""Hello, C world!\n\x00"They are started by \\ and ended by a new line.
\\Hello,\\multilined\\world!consttextThe byte of the ASCII inside the quotes.
'a'u8u16u32u64- Only x86_64i8i16i32i64- Only x86_64usizeisizebooltypevoidcomptime_int
trueandfalseundefined
The const keyword is user to assign a value to an identifier.
Its value is the current TOS. When possible the values are expanded during compilation.
"std"@importconststd10consttenstruct { x: usizex: usize } constPosFor a mutable value, use the var keyword. Variables are first undefined when created.
varnumber11!numberTo specify the type of an assignment, put it after the keyword.
10constten: i16varnumber: i16It is preferable to use const over var, thus the compiler enforce the use of const when a var is never mutated.
Variable identifiers cannot shadow function identifiers. They must start with an alphabetical letter or under score and can contain numbers later.
If you cannot fit the requirements you can use the @"..." notation.
Variables and constants only lives in the block they are defined into.
10constten// ten lives in all this filefnvoidexamplevoid {
varexample_var: i16// only lives inside the example functionstruct {
2consttwo// two only lives in this struct but can be called from the struct
} constMyStructMyStruct.two
}Integers are pushed on the stack by writing them.
10constdecimal0xFEconsthexadecimal0xfea0constlonger_lowercase_hex0o723constoctal0b1001101101001constbinary10_000_000_000constten_bilions0xFA_FF_60_10_00constsome_bytes0o7_5_5constpermissions0b0000_1111_0000_1111constmaskDecimals integers are not supported.
Operator overloading is not supported.
+- addition-- subtraction*- multiplication/- division%- reminder>>- left shift<<- right shift&- bitwise and|- bitwise or^- bitwise xor~- bitwise not++- array concatenation**- array multiplication
and- boolean andor- boolean ornot- boolean not=- equality!=- inequality<- less than>- greater than<=- less than equal>=- greater than equal
dup- duplicate the TOSdrop- delete the TOSswap- swap the top 2 elements of the stackover- copy the element below the TOSrot- rotate the top 3 elements>- use the TOS element>N- use the TOS - N element
&a- the address ofaa.*- dereference ofa!- store>1at>!var- equivalent ofval &var !
[_]u8{ 'h''e''l''l''o' } // push the array on the stackdupconstmessage// array length// accessing a field of an in-stack array does not consumes it>.len5=expect// iterate oven an array// using an array on the stack in for consumes it0swapfor>1in { // use one bellow TOS+
}
'h''e''l''l''o'++++=expect
}
// You can define var and const with an arrayvarsome_int: [100]i16// array operation only works on comptime know arrays length
[_]i32{ 12345 } constpart_onepart_one [_]i32{ 678910 } ++constall_parts// initialize an array
[_]u8{0} 10**>[3] 0=// true>.len10=// true// get the index from the stack2message[>] 'l'=expect[4][4]u8{ [_]u8{ 0123 } **4 }// null-terminated string
[_:0]u8{ 'h''e''l''l''o' }
>.len4=expect>[5] 0=expectThere are two types of pointers: single-item and many-item pointers.
*T- single-item pointer to one item- Supports deref (
ptr.*)
- Supports deref (
[*]T- many-item pointer to unknown number of items- Supports index syntax (
ptr[i]) - Supports slice syntax (
ptr[start..end]andptr[start..]) - Supports pointer arithmetic
- Supports index syntax (
Closely related to arrays and slices:
*[N]T- pointer to N items, equivalent of a pointer to an array- Supports index syntax (
array_ptr[i]) - Supports slice syntax (
array_ptr[start..end]) - Supports len (
array_ptr.len)
- Supports index syntax (
[]T- many-item pointer ([*]T) and a length (usize): a slice- Supports index syntax (
slice[i]) - Supports slice syntax (
slice[start..end]) - Supports len (
slice.len)
- Supports index syntax (
To obtain a single-item pointer, use &x.
A slice is a combination of a pointer and a length. The difference with an array is that the slice length is known at runtime.
"hello"// a string litteral is of type []const u8// accessing a field of an in-stack slice does not consumes it>.len5=// true>[5] 0=// truevararray
[_]u8{ 3210 } !array// slices have a runtime-know sizearray[0..2 :2]
>.len2=// true>[2] 2=// true// This will fail as array[2] isn't equal to 0. It will lead// to a runtime panic1array[0..> :0] // slice array from 0 to >dropNote that a Zoc file is interpreted as a struct.
struct { x: i32y: i32 } constPointPoint{ 37!x69!y }
// accessing an in-stack struct does not consumes it>.x37=// true>.y69=// truestruct {
prev: *Nodenext: *Node
} constNodestruct { a: i32b: i32 }
// the struct type is not consumed >{ 1!a2!b }
struct {
x1: usizex2: usizey1: usizey2: usize// struct can have methodsfnusizescalarProduct (*Pos) {
>.x1>.x2*>1.y1>1.y2*+
}
}It allows the field to be omitted on struct assignement.
struct {
a: i32: 1234b: i32
} constFoostruct { x: i32y: i32 } constPosvarPospos
.{ 37!.x69!.y } !posenum { oknot_ok } constTypeType.okconstcenumu8 { zeroonetwothree } constValueValue.zero@intFromEnum0=// trueValue.one@intFromEnum1=// trueValue.two@intFromEnum2=// trueValue.three@intFromEnum3=// trueenumu16 {
hundred: 100thousand: 1_000million: 1_000_000
} constValue2Value2.hundred@intFromEnum100=// trueValue2.thousand@intFromEnum1000=// trueValue2.million@intFromEnum1000000=// trueenumu8 {
a: 3bc: 0d
}
// the enum is not consumed>.a@intFromEnum3=expect>.b@intFromEnum4=expect>.c@intFromEnum0=expect>.d@intFromEnum1=expectenum {
redgreenbluefnboolisRed (Color) {
Color.red=
}
} constColorColor.redconstcolorcolorColor.isRedexecptColor.greenconstcolorcolorswitch {
.red=> { false }
.green=> { true }
.blue=> { false }
} // trueA union defines a set of possibles types that can be used by a value. Only one field can be acceded.
union {
int: i32uint: u32boolean: bool
} constPayloadvarPayloadpayloadPayload{ 10!.int } !payloadpayload.uint// really unsafe, but it works
}They are used to limit the scope of variable declarations and other builtin expressions.
Identifiers cannot be named the same as an already existing identifier in the scope.
fnvoidhello (void) { }
{
"hello hello"consthello// This will fail
}
// it's ok
{
1constnumber
}
{
2constnumber
}10consta100constbaswitch {
0=> { 0 }
// if 1, 2, 3, 4, 5, 6, 7, 8 or 9
1...9 => { 1 }
// You can switch a variable as long as it is know at comptimeb=> { 3 }
// Switch needs to handle every case possible.// A lot of time else is mandatoryelse=> { 99 }
} 99=// trueenum { redgreenblue } constColorColor.greenswitch {
Color.red=> { false }
.green=> { true } // the type is inferred.blue=> { false }
// No else as every case has been handled
} // true0while100<do {
1+
} 99=// true[_]u32{ 12345 } constitems// for loops iterates over arrays and slices0foritems[0..2] in {
+
} 3=// true// You can capture the value0foritemsinwithvalue {
// You can break or continue a for loopvalue2=if { continue }
value+
} 13=// true// Multiple values are supported// You can get the index with 0..
[_]u32{ 678910 } constitems2varresult: [5]u32foritemsitems2 0..inwithvaluevalue2 {
valuevalue2+!result[>] // >0 gets consumed by ! and >1 by >
} result[3] 13=// true0for 0..10 in {
8=if { break }
1+// else gets executed on breaking
} else {
7=// true
}4constfourfour4=if {
true
} else { unreachable }
four5%0=if {
unreachable
} four3%0=elif {
unreachable
} four2%0=elif {
true
} else { unreachable }Executes an expression on scope exit.
fnu32deferExample (void) {
vara: u322!a
{ defer { 4!a } }
a4=// true5!aa
}
deferExample5=// true Last deferred is first executed
"std"@importconststdstd.debug.printconstprintdefer { "1 "print }
defer { "2 "print }
defer { "3\n"print }
// 3 2 1Return value inside a defer expression is not allowed.
defer { 1 } // This will fail// Parameters are on the stack. The same goes for the returned value(s)fni8add (i8i8) {
+
}
fnboolgreaterThan2 (i32) { 2> }
// You can name parameters.// Named parameters are immutable.fni8sub (i8i8) withab {
ab-
}
// extern tells the compiler that exist outside the Zoc code.// Currently only C is supported.externfni32something (i32i32)
// inline inline a function instead of calling it when invoked.inlinefni32div (i32i32) { / }with is used to associate an identifier to a value in a scope. It can be used in if, elif, else, while, for, switch, defer and fn with the syntax:
<keyword>with<identifiers> { <expressions> }fnusizemain (usize*[]constu8) withargcargv {
0
}It is used for calling assembly inside the code.
fnvoidexit (void) asm {
\\mov rax, 60\\mov rdi, 0\\syscall
}
// Orfnvoidexit (void) {
asm {
\\mov rax, 60\\mov rdi, 0\\syscall
}
}Perform a syscall with n args.
0601@syscall// exitImports a file as a value.
Example:
"std"@importconststdPerforms a type coercion on a definition. It can not work.
10constten: i16teni32@asConvert a value from one type to another. The size of both types must be the same. The return type is inferred.
100-constten: i16ten@bitCastReturn the size it takes to store a type.
i64@sizeOf// 8Convert a big endian to little endian and little endian to big endian. It only works on integers types.
10constten: i16ten@byteSwapThrow an error on compilation when semantically analyzed.
"it does not compiles"@compileErrorPrints the arguments passed to it at compile time.
Example:
10constten"ten: " .{ ten } @compileLogEquivalent of a null-terminated string literal with the file content. The path is taken from the zoc file.
Example:
"file.txt"@embedFileconstfileConverts a enum value into an integer. The return type is comptime_int.
Exemple:
enumu8 { redgreenblue } constColorColor.red@intFromEnum// 0Converts an integer into an enum value. The return type is the inferred result type.
Example:
enumu8 { reggreenblue } constColorColor.green1@enumFromInt=// trueConverts an integer to another integer while keeping the same value. It can fail at runtime if there is an overflow
Example:
fni32example (i32) { ... }
10constten: i16ten@intCastexamplefalse@intFromBool// 0: u8true@intFromBool// 1: u8Converts a pointer to an int of usize.
Example:
10consti16ten&ten@intFromPtr// &ten: usizeConverts an integer of uzise to a pointer of the inferred type.
Example:
0xA00constsomethingsomething@ptrFromIntconst*i16a_thingCopies bytes from one region to another.
The destination and the source must be a mutable slice or a pointer to a mutable array. At least one of the elements must have a len field. If the two have one, they must be equal.
sourcedest@memcpyConverts a pointer of one type to the pointer of another type.
value: anytype@ptrCastanytypeReturns the number of bytes needed to store T.
T: type@sizeOfcomptime_intConverts an enum or union value of to a string literal.
Example:
enum { redgreenblue } constColorColor.red@tagName// "Color.red"Return the type where the function is called.
Example:
@ThisconstSelfitems: []u8fnvoidprintItems (*Self) withself {
self.itemsforiin {
iprinti
}
}Panic when executed in runtime
Example:
fni32div (i32i32) {
dup0=if { "Dividing by 0 is not allowed"@panic }
/
}