Cog is under active development. Not all documented language features have been implemented.
Cog is a C-like systems programming language intented to maximize modular design with simple features for enforcing and testing correctness.
- Examples
- Benchmarks
- Syntax
- Compiler
- Debugger
- Documenter
No benchmarks have been run at this time.
A formal specification of the grammar follows:
Cog implements C-like comments.
// This is a line comment
/*
This is
a block
comment
*/
Primitives except for character encodings have been implemented. Variable declaration currently limited to one variable with no inline assignment. Pointers and Arrays have not yet been implemented.
Variables are declared and assigned using a C-like syntax.
type name;
type name = value;
type name0, name1, name2;
type name0 = value0, name1 = value1, name2 = value2;
However, variable assignment returns void and must be separate statements.
Operator | Description | Implemented |
---|---|---|
a=b; | direct assignment | yes |
a+=b; | inplace add | yes |
a-=b; | inplace subtract | yes |
a*=b; | inplace multiply | yes |
a/=b; | inplace divide | yes |
a%=b; | inplace remainder | yes |
a<<=b; | inplace left shift | yes |
a>>=b; | inplace logical right shift | yes |
a>>>=b; | inplace arithmetic right shift | yes |
a<<>=b; | inplace rotate left | yes |
a>><=b; | inplace rotate right | yes |
a&=b; | inplace bitwise AND | yes |
a^=b; | inplace bitwise XOR | yes |
a|=b; | inplace bitwise OR | yes |
a and=b; | inplace boolean AND | yes |
a xor=b; | inplace boolean XOR | yes |
a or=b; | inplace boolean OR | yes |
The usual primitive types are supported with added support for fixed point numbers. Here, <n>
represents the bitwidth of the type and <m>
represents it's power of 2 exponent. For example, int32
is a 32 bit signed integer. fixed32e-5
is a 32 bit fixed point decimal with a five bit fractional precision. int<n>
, uint<n>
, fixed<n>e<m>
, and ufixed<n>e<m>
all support arbitrary values for <n>
and <m>
. However, float<n>
is implemented by the smallest of 16, 32, 64, or 128 sufficient for <n>
.
bool
int<n>
uint<n>
fixed<n>e<m>
ufixed<n>e<m>
float<n>
Both integer and decimal constants are encoded as fixed point numbers with arbitrary precision. This means that all constant expressions will evaluate at compile time without any rounding errors. Once all constant expressions are evaluated, the results are implicitly cast to and stored as the type that they are assigned in the program. Constants may be specified in base 10 as decimal values with a base 10 exponent or in base 16 or base 2 as integers.
578
63e-4
3.2e6
0x88af
0b1001
This feature is not yet implemented.
Static arrays are declared the same as in C++, the array dimensions are specified after the variable name in the declaration and may only be compile time constants. They are also indexed similary to C++. Static arrays also carry with them compile-time size attributes which may be accessed directly.
int32 myArr[32][6][3];
myArr[3][2][1] = 5;
int32 width = myArr.size[0];
int32 height = myArr.size[1];
int32 depth = myArr.size[2];
This feature is not yet implemented.
Dynamically allocated arrays are specified separately from pointers in Cog with empty array brackets. They may then be allocated as multidimensional arrays then used as expected. Finally, they must be deleted after use. Dynamic arrays carry with them size attributes that are set upon allocation.
int32 myArr[][][];
myArr = new int32[32][6][3];
myArr[3][2][1] = 5;
int32 width = myArr.size[0];
int32 height = myArr.size[1];
int32 depth = myArr.size[2];
delete myArr;
This feature is not yet implemented.
Pointers may only point to a single value.
MyStruct myPtr*;
myPtr = new MyStruct();
int32 valuePtr*;
valuePtr = myPtr*.myMember&;
valuePtr* = 3;
int32 myValue = myPtr*.myMember;
delete myPtr;
The following table lists the precedence and associativity of all supported operators in descending precedence.
Precedence | Operator | Description | Associativity | Implemented |
---|---|---|---|---|
1 | (a) | parenthesis | yes | |
2 | a::b | scope resolution | left to right | no |
3 | a() a[] a* a& type(b) a.b |
function call subscript pointer dereference address-of typecast member access |
left to right | yes no no no no no |
4 | ~a not a -a new a delete a |
bitwise not boolean not negative allocate memory deallocate memory |
right to left | yes yes yes no no |
5 | a*b a/b a%b |
multiplication division remainder |
left to right | yes yes yes |
6 | a+b a-b |
addition subtraction |
left to right | yes yes |
7 | a<<b a>>b a>>>b a<<>b a>><b |
left shift logical right shift arithmetic right shift rotate left rotate right |
left to right | yes yes yes yes yes |
8 | a&b | bitwise AND | left to right | yes |
9 | a^b | bitwise XOR | left to right | yes |
10 | a|b | bitwise OR | left to right | yes |
11 | a<b a>b a<=b a>=b a==b a!=b |
less than greater than less or equal greater or equal equal to not equal to |
left to right | yes yes yes yes yes yes |
12 | a and b | boolean AND | left to right | yes |
13 | a xor b | boolean XOR | left to right | yes |
14 | a or b | boolean OR | left to right | yes |
Primitive types are implicitly cast when doing so would not lose significant bits. This means that implicit casts can cause loss in precision.
If statements have the usual C syntax and behave as one might expect. Spacing doesn't matter, but the conditions must be wrapped in parenthesis. While single line statements in the body of an if don't need curly brackets, multiline statement blocks do.
if (x < 5) {
// Do things
} else if (x > 20) {
// Do other things
} else
// Do one statement
While loops also have the usual C syntax. Same rules as if statements apply.
while (i < 100) {
// Do things
}
while (j < 20)
// Do one statement
Member functions are not yet implemented.
Functions are declared using syntax similar to C++ but with behavior similar to Go. The receiver is specified with a scope resolution operator. However functions cannot be defined inside a structure. They must be explicitly defined outside. Functions without the receiver are defined like any other function in C++.
struct MyStruct
{
}
int32 MyStruct::myFunc(int32 myArg, int32 myArg2)
{
// Do things.
}
int32 myFunc2(int32 myArg, int32 myArg2)
{
// Do Things.
}
This feature is unstable.
Inline assembly uses AT&T syntax. Each line specifies an instruction, and each instruction has a list of comma separated operands. Registers are denoted by %name
, constants by $value
, and program variables by their name.
int32 count = 5;
int32 value = 1;
asm {
mov value, %eax
mov count, %ebx
loop:
mul $2, %eax
dec %ebx
jnz %ebx, loop
mov %eax, value
}
fixed32 inv_value = 1/fixed32(value);
.ifc
files specify limited interfaces designed for specific purposes. These represent the only way that programs may be linked, ensuring that any structure in a code base may be swapped out for something else while guaranteeing correct functionality.
Below shows an example of an interface file for a simple stack. The first line declares a templated type ValueType
and two interfaces that use that type, Node
and Stack
. Finally, it specifies a function pop()
that is only dependent upon the functionality provided by the interface and therefore automatically implemented for all structures that implement the Stack
interface.
Stack.ifc
typename ValueType;
interface Node<ValueType>
{
construct(ValueType value, Node<ValueType> prev*);
void push(Node<ValueType> list*);
Node<ValueType>* pop();
ValueType get();
}
interface Stack<ValueType>
{
void push(ValueType value);
void drop();
ValueType get();
}
ValueType Stack<ValueType>::pop()
{
ValueType result = get();
drop();
return result;
}
This feature is not yet implemented.
This feature is not yet implemented.
This feature is not yet implemented.
This defines a character encoding to be used for strings.
encode ascii
{
'\NULL': 0,
'\SOH': 1,
'\STX': 2,
...
'0-9': 48,
...
}
.cog
files each contain a structure that implements a set of interfaces. These files may import .ifc
files and create dependent types that are eventually linked in the .prg
file.
Interface files may be imported in two ways. The import
directive imports everything from the file and puts it all in the file's namespace while the 'as' directive sets the space's name. Alternatively, elements from may be imported directly via the from
directive.
Node.cog
typename ValueType;
struct Node<ValueType>
{
ValueType value;
Node<ValueType> *prev;
};
keep Node<ValueType> implements Node<ValueType> from "Stack.ifc";
Node<ValueType>::new(ValueType value, Node<ValueType> *prev)
{
this->value = value;
this->prev = prev;
}
Node<ValueType>::delete()
{
if (prev)
delete prev;
}
void Node<ValueType>::push(Node<ValueType> list*)
{
assert not prev;
prev = list;
}
Node<ValueType>* Node<ValueType>::pop()
{
Node<ValueType> result* = prev;
prev = null;
return result;
}
ValueType Node<ValueType>::get()
{
return value;
}
Stack.cog
import "Stack.ifc" as StackIfc;
typename ValueType;
struct Stack<ValueType>
{
depend Node implements StackIfc::Node<ValueType>;
suggest Node = Node<ValueType> from "Node.cog";
Node *last;
}
keep Stack<ValueType> implements StackIfc::Stack<ValueType>;
Stack<ValueType>::new()
{
}
Stack<ValueType>::delete()
{
if (last)
delete last;
}
void Stack<ValueType>::push(ValueType value)
{
Node *node = new Node(value, last);
last = node;
}
void Stack<ValueType>::drop()
{
Node *node = last;
last = last->pop();
delete node;
}
ValueType Stack<ValueType>::get()
{
return last->get();
}
This feature is not yet implemented.
.tst
test files specify an array of tests on interfaces, structures, and functions that check their implementation.
Stack.tst
depend IntStack implements Stack<int32> from "Stack.ifc";
test("myStackTest")
{
IntStack stack;
stack.push(5);
stack.push(3);
stack.push(6);
assert stack.get() == 6;
assert stack.pop() == 6;
stack.drop();
assert stack.pop() == 5;
}
This feature is not yet implemented.
Mocks are like structures in that they implement functionality of a given set of interfaces, except that they implement only the functionality necessary to execute the tests.
mock MockNode
{
};
This feature is not yet implemented.
Tests specify a contained set of logic that exercises specific functionality of structures or interfaces.
test("My Test")
{
// Do things.
}
.prg
programs specify a the final import and link configurations, and the main behavior of a binary.
typename ValueType;
import Stack<ValueType> from "Stack.cog" {
// This dependency resolution isn't necessary
// because of the suggest in Stack.cog
Node = Node<ValueType> from "Node.cog";
}
void main()
{
// Do something
}
This feature is not yet implemented.
Templated typenames may be specified at the global scope of a file, constrained for specific uses, and then used throughout the file. Interfaces, Protocols, Structures, Functions, Mocks, and Tests may all be templated. This is done with the templating operator <types...>
after their names.
typename myType1;
keep MyType in [int32, float32];
typename myType2;
keep MyType2 implements MyInterface;
struct MyStruct<MyType, MyType2>
{
MyType value0;
MyType2 value1;
};
void myFunc<MyType, MyType2>(MyType a, MyType2 b)
{
}
This feature is not yet implemented.
This feature is not yet implemented.
Static constraints proven at compile time.
keep x < y;
keep z in [a, b, c];
for (int8 i = 0; i < 10; i++) {
keep a[i] < b[i];
}
Dynamic constraints checked at run-time during tests and debugging, but excluded from production.
assert x < y;
assert z in [a, b, c];