K# (KSharp) is a C#-inspired language that compiles to Qt/KDE C++. It is the native language of the Tridentu 2 Linux platform, designed for writing Qt and KDE applications with familiar C#-style syntax.
- CMake 3.16+
- Qt6 (Core, Widgets)
- Flex
- Bison 3.x
- Inja (C++ template engine)
- nlohmann/json
- KDE Frameworks 6 (CoreAddons, I18n, XmlGui) — for KDE app support
git clone https://github.com/Tridentu/KSharp
cd KSharp
cmake -B build -S .
cmake --build buildThe compiler binary will be at build/ksharpc.
ksharpc source.kshp # Compile only
ksharpc source.kshp --build # Compile and build with CMake
ksharpc source.kshp --run # Compile, build, and run
ksharpc source.kshp --reconfigure # Force CMake reconfiguration
K# Source files use the .kshp extension. The C++ Output is written to a directory named after the first source file's base name.
K# syntax highlighting is available for Kate, KWrite, and any editor using KSyntaxHighlighting (including KDevelop).
Install the syntax definition:
mkdir -p ~/.local/share/org.kde.syntax-highlighting/syntax/
cp ksharp.xml ~/.local/share/org.kde.syntax-highlighting/syntax/Restart Kate and open any .kshp file — K# will appear under Tools → Highlighting → Sources.
A K# program consists of one or more .kshp files, each containing namespaces, classes, interfaces, and enums.
using Sys;
using Sys.Application;
namespace MyApp {
public class MyProgram : ConsoleApplication {
public static void Main(string[] args) {
Console.WriteLine("Hello from K#!");
}
}
}Namespaces group related classes together and map to C++ namespaces.
namespace MyApp {
// classes, enums, interfaces
}Dotted namespaces are supported and map to nested :: in C++:
namespace MyApp.Core {
// ...
}Import K# standard library namespaces with using:
using Sys; // Core standard library
using Sys.IO; // File system
using Sys.Application; // Application entry point types
using Sys.Tridentu; // Tridentu platform APIs
using Sys.Tridentu.UI; // UI widgets and windowsClasses compile to Qt C++ classes inheriting from QObject by default.
public class MyClass {
// ...
}With inheritance:
public class MyClass : ParentClass {
// ...
}Constructors are declared with the same name as the class and no return type:
public class MyClass {
public MyClass() {
// constructor body
}
}public abstract class MyAbstractClass {
public abstract void DoSomething();
}| K# | C++ |
|---|---|
public |
public |
private |
private |
protected |
protected |
Properties compile to Qt's Q_PROPERTY with getter, setter, and change signal.
public property int Health;
public property string Name = "Player"; // with default value
public property List<string> Items;Properties with custom accessors:
public property int Health {
get { return m_Health; }
set { m_Health = value; emit HealthChanged(value); }
}Static properties:
public static property int Count;public void DoSomething() {
// body
}
public string GetName() {
return this.Name;
}
public static void StaticMethod() {
// no this access
}Method modifiers:
public virtual void CanOverride() { }
public override void Overridden() { }
public abstract void MustImplement();Signals are Qt signals — they are emitted automatically when called:
public signal void PlayerDied();
public signal void Damaged(int amount);Slots are Qt slots:
public slot void TakeDamage(int amount) {
this.Health -= amount;
Damaged(amount);
}The compiler automatically injects emit before signal calls.
sender.SignalName += receiver.SlotName;Uses C#-style += with the property and signal name:
this.Button.clicked += this.OnButtonClicked;The compiler looks up the property type and generates the correct QObject::connect call.
| K# | C++ |
|---|---|
string |
QString |
int |
int |
float |
float |
double |
double |
bool |
bool |
char |
QChar |
void |
void |
var |
auto |
| K# | C++ |
|---|---|
List<string> |
QStringList |
List<T> |
QList<T> |
Dictionary<K,V> |
QMap<K,V> |
HashSet<T> |
QSet<T> |
Standard K#/C# control flow passes through verbatim:
if (condition) { }
else { }
while (condition) { }
for (int i = 0; i < 10; i++) { }
foreach (string item in this.Items) {
Console.WriteLine(item);
}
switch (value) {
case 1: break;
}
try {
// ...
} catch (Exception e) {
Console.Error(e.Message);
}Console.WriteLine($"Hello {this.Name}, you have {count} items.");Compiles to QString::arg() chains.
if (obj is MyClass) { }
MyClass c = obj as MyClass;Compiles to dynamic_cast.
if (obj == null) { }Compiles to nullptr.
// QObject subclass — heap allocated with parent
MyClass obj = new MyClass();
// Widget — heap allocated, parent injected correctly
PushButton btn = new PushButton("Click Me");
// Value type — stack allocated
var name = new QString("Hello");public enum PlayerState {
Idle = 0,
Walking = 2,
Running = 4,
Dead = 6
}Compiles to C++ enum class.
public interface IDrawable {
void Draw();
string GetName();
}Compiles to a C++ abstract class with pure virtual methods.
| K# | Qt equivalent |
|---|---|
Console.WriteLine(x) |
qDebug() << x |
Console.Write(x) |
qDebug().nospace() << x |
Console.ReadLine() |
QTextStream stdin read |
Console.Error(x) |
qCritical() << x |
Math.Abs(x) |
qAbs(x) |
Math.Min(a,b) |
qMin(a,b) |
Math.Max(a,b) |
qMax(a,b) |
Math.Floor(x) |
qFloor(x) |
Math.Ceil(x) |
qCeil(x) |
Math.Sqrt(x) |
qSqrt(x) |
Math.Pow(x,y) |
qPow(x,y) |
Math.Clamp(x,a,b) |
qBound(a,x,b) |
Environment.Exit(code) |
QCoreApplication::exit(code) |
Environment.GetVariable(name) |
qEnvironmentVariable(name) |
| K# | Qt equivalent |
|---|---|
File.Exists(path) |
QFile::exists(path) |
File.Delete(path) |
QFile::remove(path) |
Directory.Exists(path) |
QDir::exists(path) |
Directory.Create(path) |
QDir().mkpath(path) |
| K# | Qt equivalent |
|---|---|
MessageBox.Show(p,t,m) |
QMessageBox::information(p,t,m) |
MessageBox.Warning(p,t,m) |
QMessageBox::warning(p,t,m) |
MessageBox.Critical(p,t,m) |
QMessageBox::critical(p,t,m) |
MessageBox.Question(p,t,m) |
QMessageBox::question(p,t,m) |
| K# | Qt equivalent | Use case |
|---|---|---|
ConsoleApplication |
QCoreApplication |
Console apps |
GuiApplication |
QGuiApplication |
GUI without widgets |
WidgetApplication |
QApplication |
Widget-based apps |
TridentuApplication |
QApplication + KAboutData |
Tridentu/KDE apps |
| K# | Qt/KDE equivalent |
|---|---|
MainWindow |
QMainWindow |
KDEWindow |
KXmlGuiWindow |
Dialog |
QDialog |
Widget |
QWidget |
DockWidget |
QDockWidget |
| K# | Qt equivalent |
|---|---|
Label |
QLabel |
PushButton |
QPushButton |
LineEdit |
QLineEdit |
TextEdit |
QTextEdit |
ComboBox |
QComboBox |
CheckBox |
QCheckBox |
RadioButton |
QRadioButton |
Slider |
QSlider |
SpinBox |
QSpinBox |
| K# | Qt equivalent |
|---|---|
VBoxLayout |
QVBoxLayout |
HBoxLayout |
QHBoxLayout |
GridLayout |
QGridLayout |
When connecting native Qt signals via +=, the following signals are recognized:
| Widget | Signals |
|---|---|
PushButton |
clicked, pressed, released |
LineEdit |
textChanged, returnPressed, editingFinished |
CheckBox |
toggled, stateChanged |
Slider |
valueChanged, sliderMoved |
ComboBox |
currentIndexChanged, currentTextChanged |
SpinBox |
valueChanged |
using Sys;
using Sys.Application;
namespace MyApp {
public class Program : ConsoleApplication {
public static void Main(string[] args) {
Console.WriteLine($"Hello from K#! Args: {args}");
}
}
}using Sys;
using Sys.Application;
using Sys.Tridentu.UI;
namespace MyApp {
public class MyWindow : MainWindow {
public property Label Title;
public property PushButton ClickMe;
public property VBoxLayout Layout;
public property Widget Container;
public MyWindow() {
this.Container = new Widget();
this.Title = new Label("Hello from K#!");
this.ClickMe = new PushButton("Click Me");
this.Layout = new VBoxLayout(this.Container);
this.Layout.addWidget(this.Title);
this.Layout.addWidget(this.ClickMe);
this.setCentralWidget(this.Container);
this.resize(400, 300);
this.ClickMe.clicked += this.OnClicked;
}
public void OnClicked() {
Console.WriteLine("Button clicked!");
}
}
public class App : TridentuApplication {
public static void Main(string[] args) {
MyWindow window = new MyWindow();
window.show();
}
}
}If you want to view these examples in file form, look in the examples folder.
- No inline expression parsing — complex expressions in
newcalls may not translate correctly - No multi-file namespace merging — classes across files in the same namespace don't share type information
- No generics — K# types cannot be parameterized beyond the built-in collection types
- No lambda expressions or anonymous functions
- No
async/await - File I/O (
File.Read,File.Write) requires manualQFileusage for now Path.Combineis not supported due to argument structure differencesString.Formatis not supported — use string interpolation$"..."instead
K# is dual-licensed:
- The K# language specification and standard library are licensed under [LICENSE]
- The
ksharpccompiler is licensed under [LICENSE-COMPILER]