DSC that we can follow as a reference for implementation.
24 KiB
DSC Language Specification v1.0
Damn Simple C (DSC) - A systems programming language for the DSA architecture
Table of Contents
- Introduction
- Lexical Structure
- Types
- Variables and Declarations
- Classes
- Functions
- Expressions
- Statements
- Control Flow
- Memory Management
- Generics
- Modules and Imports
- Keywords
- Operators
- Complete Example Programs
- Grammar Summary
- Appendix A: Differences from Rust
- Appendix B: Standard Library Conventions
1. Introduction
DSC is a statically-typed systems programming language designed for the DSA (Damn Simple Architecture). It combines modern syntax with explicit memory management and direct assembly interoperability.
Design Goals
- Explicit: No hidden control flow or implicit conversions
- Simple: Easy to understand what the compiler generates
- Safe: Scope-based resource management via
defer - Interoperable: Direct assembly library integration
2. Lexical Structure
2.1 Comments
// Single-line comment
/* Multi-line
comment */
2.2 Identifiers
Identifiers must start with a letter or underscore, followed by any number of letters, digits, or underscores.
valid_identifier
_private
MyClass
get_value_42
2.3 Reserved Keywords
as break class const continue
defer else false field fn
if impl include let loop
mut priv pub return self
static struct true void while
2.4 Literals
Integer Literals
42 // Decimal
0x2A // Hexadecimal
0b101010 // Binary
0o52 // Octal
String Literals
"Hello, world!"
"Escape sequences: \n \t \\ \""
Boolean Literals
true
false
3. Types
3.1 Primitive Types
Integer Types
i8 // Signed 8-bit integer
i16 // Signed 16-bit integer
i32 // Signed 32-bit integer
i64 // Signed 64-bit integer
u8 // Unsigned 8-bit integer
u16 // Unsigned 16-bit integer
u32 // Unsigned 32-bit integer
u64 // Unsigned 64-bit integer
Boolean Type
bool // true or false
String Type
str // String literal type (pointer to null-terminated byte array)
Void Type
void // Absence of a value (only valid as return type)
3.2 Pointer Types
Pointers are declared using the & prefix:
&u32 // Pointer to u32
&bool // Pointer to bool
&Point // Pointer to Point class
Note: Pointers in DSC are raw memory addresses (like C pointers), not Rust-style references with borrow checking.
3.3 Array Types
[u32; 10] // Fixed-size array of 10 u32 values
[bool; 256] // Fixed-size array of 256 bool values
3.4 Generic Types
Vec<u32> // Generic type with one type parameter
Map<str, i32> // Generic type with multiple type parameters
4. Variables and Declarations
4.1 Variable Declaration
Variables are declared with let and are immutable by default:
let x: u32 = 42;
let name: str = "Alice";
let ptr: &u32 = &x;
4.2 Mutable Variables
Use mut to declare mutable variables:
let mut count: u32 = 0;
count = count + 1;
4.3 Type Inference
Type annotations can be omitted when the type can be inferred:
let x = 42; // Inferred as u32 (default integer type)
let name = "Bob"; // Inferred as str
5. Classes
Classes are DSC's primary means of encapsulation, combining data and behavior.
5.1 Class Definition
class ClassName {
// Class body
}
5.2 Fields
Fields represent the data members of a class. They must be explicitly marked as pub or priv:
class Point {
pub field x: u32;
pub field y: u32;
priv field cached: bool;
}
Syntax: [pub|priv] field <name>: <type>;
5.3 Constants
Constants are compile-time values associated with a class:
class Math {
pub const PI: u32 = 3;
pub const E: u32 = 2;
priv const INTERNAL_CONSTANT: u32 = 42;
}
Syntax: [pub|priv] const <NAME>: <type> = <value>;
Usage: Math::PI
5.4 Static Fields
Static fields are global variables associated with a class:
class Logger {
pub static mut INSTANCE_COUNT: u32 = 0;
priv static mut INTERNAL_STATE: u32 = 0;
}
Syntax: [pub|priv] static mut <name>: <type> = <value>;
Usage:
Logger::INSTANCE_COUNT = Logger::INSTANCE_COUNT + 1;
Note: All static fields must be declared mut as they represent mutable global state.
5.5 Methods
Methods are functions associated with a class. Instance methods take a self parameter:
class Point {
pub field x: u32;
pub field y: u32;
// Constructor (static method)
pub fn new(x: u32, y: u32) -> Point {
return Point { x: x, y: y };
}
// Instance method
pub fn get_x(self: &Point) -> u32 {
return self.x;
}
// Mutable instance method
pub fn set_x(self: &Point, value: u32) {
self.x = value;
}
// Static method
pub fn zero() -> Point {
return Point { x: 0, y: 0 };
}
}
Method Syntax:
- Instance method:
[pub|priv] fn <name>(self: &<ClassName>, ...) -> <return_type> - Static method:
[pub|priv] fn <name>(...) -> <return_type>
5.6 Class Instantiation
// Using constructor
let p1: Point = Point::new(10, 20);
// Using struct literal syntax
let p2: Point = Point { x: 5, y: 15 };
5.7 Method Calls
let p: Point = Point::new(10, 20);
// Method call syntax (preferred)
let x: u32 = p.get_x();
// Explicit syntax (equivalent)
let x: u32 = Point::get_x(&p);
// Static method call
let origin: Point = Point::zero();
5.8 Field Access
let p: Point = Point { x: 10, y: 20 };
// Read field
let x: u32 = p.x;
// Write field (if mutable)
let mut p2: Point = Point { x: 0, y: 0 };
p2.x = 42;
5.9 Complete Class Example
class Vec {
priv field data: &u32;
priv field len: u32;
priv field capacity: u32;
pub const DEFAULT_CAPACITY: u32 = 16;
pub static mut TOTAL_ALLOCATIONS: u32 = 0;
pub fn new(capacity: u32) -> Vec {
Vec::TOTAL_ALLOCATIONS = Vec::TOTAL_ALLOCATIONS + 1;
return Vec {
data: alloc::malloc(capacity * 4) as &u32,
len: 0,
capacity: capacity
};
}
pub fn with_default_capacity() -> Vec {
return Vec::new(Vec::DEFAULT_CAPACITY);
}
pub fn push(self: &Vec, item: u32) {
if self.len == self.capacity {
self.grow();
}
*(self.data + self.len) = item;
self.len = self.len + 1;
}
pub fn get(self: &Vec, index: u32) -> u32 {
return *(self.data + index);
}
priv fn grow(self: &Vec) {
let new_capacity: u32 = self.capacity * 2;
let new_data: &u32 = alloc::malloc(new_capacity * 4) as &u32;
// Copy old data
let mut i: u32 = 0;
while i < self.len {
*(new_data + i) = *(self.data + i);
i = i + 1;
}
alloc::free(self.data as u32);
self.data = new_data;
self.capacity = new_capacity;
}
pub fn drop(self: &Vec) {
alloc::free(self.data as u32);
}
}
6. Functions
6.1 Function Declaration
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// Function body
}
6.2 Return Type
Functions without a return value use void or omit the return type:
fn print_message(msg: str) -> void {
// No return value
}
// Equivalent
fn print_message(msg: str) {
// No return value
}
6.3 Return Statement
fn add(a: u32, b: u32) -> u32 {
return a + b;
}
6.4 Function Parameters
fn process(value: u32, ptr: &u32, flag: bool) -> u32 {
// ...
}
7. Expressions
7.1 Literals
42 // Integer literal
0xFF // Hex literal
true // Boolean literal
"hello" // String literal
7.2 Binary Operations
a + b // Addition
a - b // Subtraction
a * b // Multiplication
a / b // Division
a % b // Modulo
7.3 Comparison Operations
a == b // Equal
a != b // Not equal
a < b // Less than
a > b // Greater than
a <= b // Less than or equal
a >= b // Greater than or equal
7.4 Logical Operations
a && b // Logical AND
a || b // Logical OR
!a // Logical NOT
7.5 Bitwise Operations
a & b // Bitwise AND
a | b // Bitwise OR
a ^ b // Bitwise XOR
~a // Bitwise NOT
a << b // Left shift
a >> b // Right shift
7.6 Address-of and Dereference
&x // Address-of (get pointer to x)
*ptr // Dereference (read/write through pointer)
7.7 Type Casting
value as u32 // Cast value to u32
ptr as &u8 // Cast pointer type
7.8 Field Access
obj.field // Access field of object
ptr.field // Access field through pointer (implicit dereference)
7.9 Method Call
obj.method(args) // Instance method call
Class::method(args) // Static method call
7.10 Array Indexing
arr[index] // Access array element
8. Statements
8.1 Expression Statement
function_call();
x = y + 1;
8.2 Variable Declaration
let x: u32 = 42;
let mut y: u32 = 0;
8.3 Assignment
x = 10;
*ptr = 20;
obj.field = 30;
arr[0] = 40;
8.4 Compound Assignment
x += 5; // x = x + 5
x -= 3; // x = x - 3
x *= 2; // x = x * 2
x /= 4; // x = x / 4
9. Control Flow
9.1 If Statement
if condition {
// then block
}
if condition {
// then block
} else {
// else block
}
if condition1 {
// block 1
} else if condition2 {
// block 2
} else {
// block 3
}
9.2 While Loop
while condition {
// loop body
}
9.3 Loop (Infinite Loop)
loop {
// infinite loop
if should_exit {
break;
}
}
9.4 Break and Continue
while condition {
if skip_condition {
continue; // Skip to next iteration
}
if exit_condition {
break; // Exit loop
}
}
10. Memory Management
10.1 Defer Statement
The defer keyword schedules a statement to execute when the current scope exits.
fn example() {
let ptr: u32 = alloc::malloc(256);
defer alloc::free(ptr);
// Use ptr...
if error {
return; // defer runs here
}
// More work...
} // defer runs here
10.2 Defer Execution Rules
- Scope-based: Defers execute when their enclosing scope exits
- LIFO order: Multiple defers execute in reverse order of declaration
- All exit paths: Defers run on return, break, continue, or natural scope end
fn nested_defers() {
let a: u32 = alloc::malloc(100);
defer alloc::free(a); // Runs second
if condition {
let b: u32 = alloc::malloc(200);
defer alloc::free(b); // Runs first (if in this scope)
// ... work ...
} // b's defer runs here
// ... more work ...
} // a's defer runs here
10.3 Defer with Methods
class Resource {
priv field handle: u32;
pub fn acquire() -> Resource {
return Resource { handle: alloc::malloc(1024) };
}
pub fn release(self: &Resource) {
alloc::free(self.handle);
}
}
fn use_resource() {
let res: Resource = Resource::acquire();
defer res.release();
// Use resource...
} // res.release() automatically called
11. Generics
11.1 Generic Classes
class Box<T> {
priv field value: T;
pub fn new(value: T) -> Box<T> {
return Box { value: value };
}
pub fn get(self: &Box<T>) -> T {
return self.value;
}
pub fn set(self: &Box<T>, value: T) {
self.value = value;
}
}
11.2 Generic Usage
let int_box: Box<u32> = Box::new(42);
let str_box: Box<str> = Box::new("hello");
let value: u32 = int_box.get();
11.3 Multiple Type Parameters
class Pair<T, U> {
pub field first: T;
pub field second: U;
pub fn new(first: T, second: U) -> Pair<T, U> {
return Pair { first: first, second: second };
}
}
let p: Pair<u32, str> = Pair::new(42, "answer");
11.4 Generic Functions
fn swap<T>(a: &T, b: &T) {
let temp: T = *a;
*a = *b;
*b = temp;
}
let mut x: u32 = 1;
let mut y: u32 = 2;
swap(&x, &y);
11.5 Generic Constraints
Note: DSC does not support trait bounds or where clauses. Generic types are instantiated via monomorphization without constraints.
12. Modules and Imports
12.1 Assembly Imports
DSC can import assembly modules for low-level operations:
include print: "./lib/io/print.dsa";
include alloc: "./lib/memory/alloc.dsa";
Syntax: include <namespace>: "<path>";
12.2 Using Imported Functions
print::println("Hello, world!");
let ptr: u32 = alloc::malloc(256);
13. Keywords
13.1 Complete Keyword List
| Keyword | Purpose |
|---|---|
as |
Type casting |
break |
Exit loop |
class |
Class definition |
const |
Constant declaration |
continue |
Next loop iteration |
defer |
Defer statement execution |
else |
Alternative branch |
false |
Boolean literal |
field |
Class field declaration |
fn |
Function declaration |
if |
Conditional statement |
impl |
Reserved for future use |
include |
Import assembly module |
let |
Variable declaration |
loop |
Infinite loop |
mut |
Mutable binding |
priv |
Private visibility |
pub |
Public visibility |
return |
Return from function |
self |
Instance reference |
static |
Static field declaration |
struct |
Reserved for future use |
true |
Boolean literal |
void |
No return type |
while |
While loop |
14. Operators
14.1 Operator Precedence (Highest to Lowest)
| Precedence | Operators | Associativity |
|---|---|---|
| 1 | () [] . :: |
Left to right |
| 2 | ! ~ & * (unary) - (unary) |
Right to left |
| 3 | as |
Left to right |
| 4 | * / % |
Left to right |
| 5 | + - |
Left to right |
| 6 | << >> |
Left to right |
| 7 | & |
Left to right |
| 8 | ^ |
Left to right |
| 9 | ` | ` |
| 10 | == != < > <= >= |
Left to right |
| 11 | && |
Left to right |
| 12 | ` | |
| 13 | = += -= *= /= |
Right to left |
14.2 Operator Summary
Arithmetic Operators
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo
Comparison Operators
== Equal
!= Not equal
< Less than
> Greater than
<= Less than or equal
>= Greater than or equal
Logical Operators
&& Logical AND
|| Logical OR
! Logical NOT
Bitwise Operators
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
~ Bitwise NOT
<< Left shift
>> Right shift
Memory Operators
& Address-of
* Dereference
Assignment Operators
= Assignment
+= Add and assign
-= Subtract and assign
*= Multiply and assign
/= Divide and assign
15. Complete Example Programs
15.1 Hello World
include print: "./lib/io/print.dsa";
fn main() -> void {
print::println("Hello, world!");
}
15.2 Vector Implementation
include alloc: "./lib/memory/alloc.dsa";
class Vec<T> {
priv field data: &T;
priv field len: u32;
priv field capacity: u32;
pub const DEFAULT_CAPACITY: u32 = 16;
pub fn new(capacity: u32) -> Vec<T> {
let size: u32 = capacity * 4; // Assume sizeof(T) = 4
return Vec {
data: alloc::malloc(size) as &T,
len: 0,
capacity: capacity
};
}
pub fn push(self: &Vec<T>, item: T) {
if self.len >= self.capacity {
self.resize();
}
*(self.data + self.len) = item;
self.len = self.len + 1;
}
pub fn get(self: &Vec<T>, index: u32) -> T {
return *(self.data + index);
}
pub fn len(self: &Vec<T>) -> u32 {
return self.len;
}
priv fn resize(self: &Vec<T>) {
let new_capacity: u32 = self.capacity * 2;
let new_size: u32 = new_capacity * 4;
let new_data: &T = alloc::malloc(new_size) as &T;
let mut i: u32 = 0;
while i < self.len {
*(new_data + i) = *(self.data + i);
i = i + 1;
}
alloc::free(self.data as u32);
self.data = new_data;
self.capacity = new_capacity;
}
pub fn drop(self: &Vec<T>) {
alloc::free(self.data as u32);
}
}
fn main() -> void {
let vec: Vec<u32> = Vec::new(10);
defer vec.drop();
vec.push(1);
vec.push(2);
vec.push(3);
let mut i: u32 = 0;
while i < vec.len() {
let value: u32 = vec.get(i);
// Use value...
i = i + 1;
}
}
15.3 Resource Management Example
include alloc: "./lib/memory/alloc.dsa";
include print: "./lib/io/print.dsa";
class File {
priv field handle: u32;
priv field is_open: bool;
pub fn open(path: str) -> File {
let handle: u32 = 0; // Platform-specific open
return File { handle: handle, is_open: true };
}
pub fn write(self: &File, data: str) {
if !self.is_open {
return;
}
// Write implementation...
}
pub fn close(self: &File) {
if self.is_open {
// Platform-specific close
self.is_open = false;
}
}
}
fn process_file(path: str) -> bool {
let file: File = File::open(path);
defer file.close(); // Ensures file is closed on all exit paths
let buffer: u32 = alloc::malloc(1024);
defer alloc::free(buffer);
if !file.is_open {
print::println("Failed to open file");
return false; // Defers run: free(buffer), file.close()
}
file.write("Hello, file!");
return true; // Defers run: free(buffer), file.close()
}
fn main() -> void {
let success: bool = process_file("output.txt");
if success {
print::println("File processed successfully");
} else {
print::println("File processing failed");
}
}
16. Grammar Summary
16.1 EBNF Grammar
program = { import_statement | class_definition | function_definition }
import_statement = "include" identifier ":" string_literal ";"
class_definition = "class" identifier [ generic_params ] "{" { class_member } "}"
class_member = field_declaration
| const_declaration
| static_declaration
| method_declaration
field_declaration = visibility "field" identifier ":" type ";"
const_declaration = visibility "const" IDENTIFIER ":" type "=" expression ";"
static_declaration = visibility "static" "mut" identifier ":" type "=" expression ";"
method_declaration = visibility "fn" identifier [ generic_params ] "(" parameters ")" [ "->" type ] block
function_definition = "fn" identifier [ generic_params ] "(" parameters ")" [ "->" type ] block
generic_params = "<" identifier { "," identifier } ">"
parameters = [ parameter { "," parameter } ]
parameter = identifier ":" type
visibility = "pub" | "priv"
type = primitive_type
| pointer_type
| array_type
| generic_type
| identifier
primitive_type = "u8" | "u16" | "u32" | "u64"
| "i8" | "i16" | "i32" | "i64"
| "bool" | "str" | "void"
pointer_type = "&" type
array_type = "[" type ";" integer_literal "]"
generic_type = identifier "<" type { "," type } ">"
block = "{" { statement } "}"
statement = let_statement
| expression_statement
| if_statement
| while_statement
| loop_statement
| return_statement
| defer_statement
| break_statement
| continue_statement
let_statement = "let" [ "mut" ] identifier ":" type "=" expression ";"
defer_statement = "defer" expression ";"
if_statement = "if" expression block [ "else" ( if_statement | block ) ]
while_statement = "while" expression block
loop_statement = "loop" block
return_statement = "return" [ expression ] ";"
break_statement = "break" ";"
continue_statement = "continue" ";"
expression_statement = expression ";"
expression = assignment_expression
assignment_expression = logical_or_expression [ assignment_operator assignment_expression ]
assignment_operator = "=" | "+=" | "-=" | "*=" | "/="
logical_or_expression = logical_and_expression { "||" logical_and_expression }
logical_and_expression = equality_expression { "&&" equality_expression }
equality_expression = relational_expression { equality_operator relational_expression }
equality_operator = "==" | "!="
relational_expression = bitwise_or_expression { relational_operator bitwise_or_expression }
relational_operator = "<" | ">" | "<=" | ">="
bitwise_or_expression = bitwise_xor_expression { "|" bitwise_xor_expression }
bitwise_xor_expression = bitwise_and_expression { "^" bitwise_and_expression }
bitwise_and_expression = shift_expression { "&" shift_expression }
shift_expression = additive_expression { shift_operator additive_expression }
shift_operator = "<<" | ">>"
additive_expression = multiplicative_expression { additive_operator multiplicative_expression }
additive_operator = "+" | "-"
multiplicative_expression = cast_expression { multiplicative_operator cast_expression }
multiplicative_operator = "*" | "/" | "%"
cast_expression = unary_expression [ "as" type ]
unary_expression = postfix_expression
| unary_operator unary_expression
unary_operator = "!" | "~" | "&" | "*" | "-"
postfix_expression = primary_expression { postfix_operator }
postfix_operator = "." identifier [ call_suffix ]
| "::" identifier [ call_suffix ]
| "[" expression "]"
| call_suffix
call_suffix = "(" [ arguments ] ")"
arguments = expression { "," expression }
primary_expression = identifier
| literal
| "(" expression ")"
| struct_literal
struct_literal = identifier "{" [ field_init { "," field_init } ] "}"
field_init = identifier ":" expression
literal = integer_literal
| string_literal
| boolean_literal
boolean_literal = "true" | "false"
Appendix A: Differences from Rust
While DSC uses Rust-like syntax, there are key differences:
| Feature | Rust | DSC |
|---|---|---|
| Encapsulation | struct + impl |
class |
| Field syntax | field: Type |
field field: Type; |
| References | Borrow-checked &T |
Raw pointers &T |
| Ownership | Compile-time borrow checker | Manual with defer |
| Traits | Supported | Not supported |
| Lifetimes | Supported | Not supported |
| Pattern matching | Supported | Not supported |
| Enums | Sum types with data | Not yet supported |
| Modules | Native | Assembly imports only |
Appendix B: Standard Library Conventions
By convention, standard library modules should provide:
Allocation Module (alloc)
include alloc: "./lib/memory/alloc.dsa";
// Functions:
// alloc::malloc(size: u32) -> u32
// alloc::free(ptr: u32) -> void
Print Module (print)
include print: "./lib/io/print.dsa";
// Functions:
// print::print(msg: str) -> void
// print::println(msg: str) -> void
// print::print_num(n: u32) -> void
// print::print_hex(n: u32) -> void
End of DSC Language Specification v1.0