Language status
What is implemented, and the decisions that the design document leaves open. Features marked (self-hosted) were added after the first compiler (C++, retired after 0.04) was frozen, see ../compiler.md.
Implemented
Section titled “Implemented”| Area | Status |
|---|---|
Namespaces (namespace A.B;), using, multiple files, global symbol resolution |
✔ |
Global variables (zero value or an initializer, running before Main) |
✔ |
Structs (value semantics), initializers, new T(), methods, static methods, nested structs |
✔ |
Visibility via a _ prefix (private) for fields and methods |
✔ |
Doc comments (///, //!) with Markdown, links and tags; cshiftc doc |
✔ |
| Struct inheritance (one base, the base comes first in the layout), upcasting, hiding methods | ✔ |
| Interfaces (methods), several per struct, checking the implementation | ✔ |
Sum types: union Shape : IShape { Circle, Rect } (inline with a tag, is/switch, interface methods dispatched on the tag) |
✔ (self-hosted, sum types) |
Interfaces as ref/const ref parameters: dynamic dispatch through a method table, no allocation, x is S s |
✔ (self-hosted, interfaces) |
| Generics: structs and functions, monomorphization, type inference, explicit type arguments | ✔ |
Constraints (where T : IComparable<T>), checked at compile time |
✔ |
| Enums with a mandatory base type and explicit values | ✔ |
Enum<T>.Count, .Min, .Max, .Values, .Names (constants) |
✔ (self-hosted, enums) |
const string S = embed("file");: a file’s exact content as a constant, found next to the source file or in the project folder; embed("*.txt")/embed_filenames("*.txt") give the contents/names of all matching files as a const ReadOnlySlice<string>, embed_lines("file") the lines of a file |
✔ (self-hosted, constants) |
Constant slices: const ReadOnlySlice<int> P = [2, 3, ..Q];, indexing/slicing/Length in constants |
✔ (self-hosted, constants) |
Collection expressions: [a, b, ..c] as T[], Slice<T>, List<T>, HashSet<T> or any struct with Create()/Add(T) |
✔ (self-hosted, collection expressions) |
Fixed<T, N>: fixed-size arrays stored inline (values: on the stack, inside structs), C arrays in structs imported as Fixed |
✔ (self-hosted, fixed-size arrays) |
Panics show where they happened (file:line:column in Function), index checks the index and the length, a standard library function that rejects its arguments also where the program called it |
✔ (self-hosted, basics) |
Slices: a[i..j], a[..j], a[i..], a[^n]; views Slice<T>, ReadOnlySlice<T> and StringSlice (no copy, ToString()/ToArray() copy) |
✔ (self-hosted, slices) |
Error enums (error E { ... }), error(E.X), typed results Error<T, E> / E<T>, is E code, case E.X: |
✔ (self-hosted, error handling) |
ARC for strings and arrays (reference semantics, Clone()), including inside structs/Error/Optional |
✔ |
Strings: UTF-8, immutable, +, ==, [i], Length, Substring, CStr() |
✔ |
Error<T> / Optional<T> (never a bare condition), is T x, is error e, is not, is null, switch patterns, try; nesting only as Error<Optional<T>> |
✔ (error handling) |
a ?? b and x ??= v for Optional<T> |
✔ (error handling) |
IDisposable + using (declaration and block form; also on return/break/continue/try) |
✔ |
ref / const ref (value, read-only alias, alias) |
✔ |
Primitive types with aliases (int=int32, …), bool, char (= uint8), nint/nuint (pointer-sized) |
✔ |
Checked integer arithmetic (overflow, division by zero, array/string bounds → panic), unchecked, --unchecked |
✔ |
Integer arithmetic in the target type (uint8 r = a + 1; without a cast) |
✔ (basics) |
Operators and precedence like C# (without ++/--), ?:, casts, sizeof |
✔ |
if/while/do/for/foreach (arrays, strings)/switch/break/continue/return |
✔ |
| Function overloading | ✔ |
C FFI: extern "C", variadic functions (printf), link "lib", pointers |
✔ |
Function values Action<…>/Func<…,R>, C-compatible |
✔ (extension) |
| Lambdas and closures (captures are read-only copies) | ✔ (self-hosted, functions) |
String interpolation $"a {x} b" (concatenation; string ToString() of structs), number formats and alignment (ToString("F2"), {x,8:F2}) |
✔ (self-hosted, strings) |
Indexers: x[k] calls Get(k), x[k] = v calls Set(k, v) (List, Dictionary, own structs) |
✔ (self-hosted) |
Importing C headers: using Name from "header.h"; (libclang, a .ffi cache), nint/nuint, structs by value |
✔ (see ../ffi.md) |
unsafe: pointers, &, *, pointer arithmetic, Memory.Allocate/Free, Memory.VolatileRead/VolatileWrite |
✔ |
unsafe on functions and methods (unsafe void F()) and before a single statement (unsafe Memory.Free(p);) |
✔ |
new { ... } / new() with the type taken from where it is used (declarations, assignments, return, arguments) |
✔ |
Entry point: int Main(), void Main(), Error<int> Main() |
✔ |
Error<void> (a result with no value; return; or falling off the end of the function = success) |
✔ (extension) |
Top-level const, default(T), foreach over structs with Count()/Get(int) |
✔ (extension) |
Standard library: List<T>, Dictionary<K,V>, File, Directory, Path, Encoding, Math, Random, string helpers |
✔ (see ../stdlib.md) |
Real OS threads: thread functions (only callable via start), Thread/Thread<T> (Join/Cancel/is), SharedPtr<T>; strings copied into threads, Mutex<T> |
✔ (extension, see threading.md; strings and Mutex<T> self-hosted) |
Interpretation and extensions beyond the design
Section titled “Interpretation and extensions beyond the design”The design document leaves a number of things open; these are the decisions that were made:
- Creating errors:
return error("text");orerror("text", code);Error<T>has.Messageand.Code. - Implicit conversion
T → Error<T>/T → Optional<T>;nullstands for “no value” (Optional). - Exhaustive
switch: a switch withoutdefault:over an enum, a union or the codes of anError<T, E>must handle every member (compile error otherwise). - Nested control statements need braces: the body of
if/else/while/do/for/foreach/using (...)may be one statement without braces, but not another control statement (if (a) if (b) F();is an error;else ifis fine). - No bool semantics:
Error<T>andOptional<T>are not conditions (if (x),!x,&&,||,?:are errors). A result is tested withx is error e/x is T v, an optional value withx is T v/x == null. is/casepatterns:x is int vbinds the value;x is error ematches a failure and binds the whole result (a pattern of the value’s own type would always match and is an error). Pattern variables are scoped to theif/while, or to thecase.x is not Pnegates a pattern; its binding (if (x is not T v) return;) is usable in theelsebranch and after anifwhose branch cannot complete.x is null/x is not nullmeanx == null/x != null.tryinint Main(): in the design’s target picture,tryis used in anintfunction. There, an error printserror: <text>to stderr and ends the program with exit code 1.- Integer arithmetic works like in C#: types smaller than 32 bits are widened to
int(auint8,uint16orcharthat meets auint32oruint64takes on that type, as in C#); a literal adapts to the other operand (uint8 x = 200; int y = x * 3;gives 600). Explicit casts never abort (they wrap/saturate); only+ - * / %are checked. - Interfaces are not value types (a value would need a hidden allocation): they are generic constraints and the
types of
ref/const refparameters (a pointer to the struct and its method table). Forconst refthe caller passes a copy on its stack. Error<Optional<T>>is the only allowed nesting. Like the others it is not a condition: writer is error e,r is T v(succeeded with a value) orr is Optional<T> o(succeeded).- Contextual keywords:
threadandwhereare only keywords where they start a thread function or a constraint; elsewhere they are ordinary names. - “Color Color”: a variable or field may have the name of its own type;
Color.Greenthen means the type unlessGreenis a member of the value (C#’s rule). - Floats as text are the shortest text that reads back as the same value (
0.1,0.30000000000000004), at run time and in constants. - Methods on
const refobjects operate on a copy (like C#’sin), so the read-only guarantee holds. - Built in (generated directly by the compiler as IR, no runtime library):
Console.Write/WriteLine,Memory.Allocate/Free/VolatileRead/VolatileWrite,Environment.Exit/Panic,Array.Copy,string.FromBytes,ToString()/CompareTo()/Equals()/GetHashCode()on numbers,int.MaxValue/MinValue(files are embedded with the keywordsembed/embed_filenames/embed_lines). Everything else is in the standard library or comes viaextern "C". Error<void>:Error<void> Save() { ... return; }.try Save();only checks for an error; there is noOptional<void>.- Constants:
const int MyConst = 5;at the top level or inside functions. Numbers,bool,char, enums,stringand constant slices (ReadOnlySlice<T>of those) are allowed, arrays are not; a constant must always be initialized, and the initializer consists only of literals, operators, casts, enum values and other constants (const Color Fav = Color.Green;,const Flags Rw = Flags.Read | Flags.Write;,const int Sum = A * 2 + 1;). Top-level constants may be used before their declaration and are always checked, even if nothing uses them; they can also be accessed qualified (Math.PI). Local constants have no storage (assigning to one is an error) and may shadow a name from an enclosing block. The compiler computes constants at compile time (selfhost/src/CodeGen/ConstEval.csh), following the same rules as the code that would be generated for the expression at run time (the type of literals, promotion of small integers, shifts, comparisons, casts that saturate floating-point values, string concatenation with numbers,sizeof(T)), but it reports overflow (2147483647 + 1), division by zero, andMIN / -1as compile errors. The same expressions are allowed for the values of enum members (B = A * 2,C = sizeof(int64));&&/||only evaluate the right side when it can change the result. - Global variables:
int Counter;,string Name = "x";,List<string> Names = List<string>.Create();at the top level, of any type. Without an initializer, the variable starts at its zero value. Initializers are arbitrary expressions; they run beforeMain, in the order of the declarations (files in the order they’re given to the compiler). The order is checked: an initializer must not use a global that’s initialized later (or itself) — not even through functions it calls (function pointers it creates count too). A global with no initializer (zero value) can be read at any time. Name resolution works like for constants (the file’s namespace,using, qualified asNs.Counter). Globals are ordinary lvalues (assign to them,ref,&inunsafe, modify fields/elements, call methods; a function-pointer global calls like a function). Values that own heap blocks (strings, arrays, lists, …) are released onceMainreturns. Novar(the type must be written out). Athreadfunction may never read or write a global, even through a function it calls (checked at compile time). foreachover structs: works for any struct withint Count()andT Get(int index)(e.g.List<T>).- Name resolution works like in C#: the current file’s namespaces and the global namespace win over
usingnamespaces. - Function values:
Action,Action<T1, …>(no result) andFunc<R>,Func<T1, …, R>(the last type is the result) are built-in types like in C#, with up to 16 parameters. They hold a function or a lambda (closures); you can assign the name of a free function or astaticmethod (Func<int, int> f = Square;,var g = Add;,Handlers.Triple). The signature must match exactly; for overloads and generic functions (Identity<int>, or inferred from the target type), the target type picks the match. Call withf(x),obj.Callback(x)(a field),table[i](https://github.com/Robert-Schneckenhaus/CShift/blob/release/v0.32/docs/language/x), orf.Invoke(x).nullis allowed; callingnullis a panic. Compare with==/!=. Function pointers are ordinary values (fields, arrays, parameters, return values, type arguments). There are norefparameters, and instance methods can’t be assigned. They’re C-compatible: passed to C (see ../ffi.md), C calls the CShift function directly; a function pointer returned from C can be called directly.(void*)casts needunsafe. - Extra syntax allowed:
cond ? a : b,new int[3][](jagged arrays),new T[] { ... },sizeof(T).