Cosmo is a language replacing awful C++'s compile-time magics. This is only some thinking about getting rid of C++ under the fact that we cannot get rid of using C++ libraries.
It sees cosmo functions and evaluate the type parts in the functions. The resulting functions is simply generated to C++ code.
import "@lib/c++/vector";
// A function returning a type
def CppVec[T]: Type = cstd.vector(T);
def main() = {
val vec = CppVec(u8)();
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
println(vec.size());
}
Output:
3
Requirements:
- scala 3.3.3
- sbt
- C++ compiler supporting C++17
- MSVC, Clang, or GCC
yarn compile && node cmd/cosmo/main.js run samples/HelloWorld/main.cos
Demonstration:
- Literals
- Integer
- Float
- Boolean
- String
- Compound Literals
- Template Literals
- Char
- Bytes
- Byte
- Array
- Dict
- Lambda
- Declarations and Statements
- Variable
- Function
- Class
- Enum Class
- Trait
- Impl
- Import
- Expressions
- Binary
- Unary
- As
- Match
- If
- For
- While
- Loop
- Break/Continue/Return
- Block
- Decorators/Macros
- Decorator
- Macro
Value Semantics:
- Literals
- TodoLit
- BoolLit
- IntLit
- i32
- bigint
- FloatLit
- StringLit
- SelfVal, SelfTy
- Identifier
- argsLit
- Control Flow
- block
- Loop
- While
- For
- Break
- Continue
- Return
- If
- Operations
- valueExpr
- typeExpr
- unOp
- RefMut
- Ref
- Mut
- deref
- binOp
- select
- asExpr
- matchExpr
- apply
- applyCTypes
- applyFunc
- applyClass
- applyType
- applyTemplate
- keyedPair
- decorate
- Declarations
- import
- varItem
- defItem
- classItem
- implItem
Type Operations:
- associateImpl
- cast
- castArgs
- castTo
- eval
- lift
- coerce
- normalize
- isSubtype
Type Guards:
- checkedMut
See Design Docs.
External types can be handled by builtin external
function:
def CppVec(T: Type): Type = cstd.vector(T);
Function body can be a type:
def Source /* inferred as : Type */ = class {
val data = Vec(u8)
}
def Pair(Lhs: Type, Rhs: Type) /* inferred as : Type */ = (Lhs, Rhs);
Lifted values must be known and evaluated at compile-time
def lift(implicit T: Type)(v: T) = Type(v);
val True = lift(true);
// or
val False = Type(false);
The signature of lift
looks a bit unfamiliar, but when we rewrite this with constraint list syntax, it looks like this:
def lift[T](v: T) = Type(v);
Or simply as:
type lift = Type;
This tells us the "template arguments" in Cosmo, such as [T]
, are the first arguments. The T
is the first parameter of lift
and has the type Type
, which is the type of all cosmo types of values. When parameters are given, the cosmo compiler will evaluate these parameters at compile time and generate remaining part as a runtime function. In particular, functions whose parameters are all types, like lift
, will be evaluated at compile time completely.
Traits are classes containing unimplemented methods, while you can provide default impls:
trait Unsigned[T] {
def asUint64(&self): u64 = staticCast(u64)(self);
}
Constraints are compile-time assertions containing type expressions:
trait Unsigned[T] {
assert(T == u8 or T == u16 or T == u32 or T == u64);
def asUint64(&self): u64 = staticCast(u64)(self);
}
You can also put constraints inside of the constraint list:
trait Unsigned[T, T == u8 or T == u16 or T == u32 or T == u64] {}
or simply as:
trait Unsigned[T <: u8 | u16 | u32 | u64] {}
Constructing a type from a type expression:
def RoundBits[T] = if (IsUnsigned(T)) {
u64
} else {
i64
}
Enum classes and pattern matching share a same syntax:
// Enum class will be translated into tagged union
class Nat {
case Zero
case Succ(Nat)
}
def add(A: Nat, B: Nat): Nat = A match {
case Zero => B
case Succ(B) => Succ(Add(A, B))
}
GADT is also supported:
class VecGADT[n: u32, T] {
case Nil: VecGADT[0, T]
case Cons(T, VecGADT[n - 1, T]): VecGADT[n, T]
}
impl[n: u32, T] VecGADT[n, T] {
def concat[m: u32](self, v: VecGADT[m, T]): VecGADT[n + m, T] = self match {
case Nil => v
case Cons(h, t /* n - 1 */) => Cons(h, t.concat(v) /* n - 1 + m */) // n + m
}
}
The runtime behavior depends on C++.