Syntax and binding
DirectSharp source files use .ds. An .inc file is a preprocessing fragment rather than an independent compilation unit. Source is UTF-8; a BOM is permitted. Indentation helps readers but does not create scope.
Identifiers and case
Identifiers use [A-Za-z_][A-Za-z0-9_]*. Symbol identity is case-insensitive and culture-independent. customer, Customer and CUSTOMER identify the same symbol. A second declaration differing only in case is a duplicate, not a separate local.
Use @ to escape a keyword as a name. The prefix changes token interpretation, not symbol identity. It does not permit spaces, punctuation or Unicode in an ordinary identifier.
let @class = "Retail"
Global.System.Console.WriteLine(@CLASS)
Reference C# after DirectSharp binding:
var @class = "Retail";
global::System.Console.WriteLine(@class);
The backend must preserve DirectSharp symbol identity even though C# itself has case-sensitive identifiers. Imported member collisions must be diagnosed rather than chosen by declaration order.
Statements and blocks
Newline normally separates statements. Semicolon is also a separator/terminator. Expressions continue across a newline when their syntax is incomplete; no backslash continuation marker is needed.
let subtotal = 100
let tax = 20
let total =
subtotal +
tax
end closes a block. Canonical source supplies the matching qualifier, such as end if, end action, end class or end for_each. The qualifier verifies the construct; end_if is not the spelling.
A post-test repeat is closed by its trailing until condition or while condition, with no following end repeat. Counted repetition and repeat forever use end repeat. See loop semantics.
Unqualified break and bare exit are equivalent: both leave the innermost loop. Sequential targeted exits use exit for, exit while or exit repeat to leave the nearest enclosing loop of that kind; exit for includes for_each. An unmatched target is an error. exit try instead completes a structured try body early.
Numeric range headers use for_each i in range start to end, with optional step expression, and close with end for_each. There is no separate for i = ... statement. Loop-shaping clauses use the order where, order_by, skip, take; for remains the target word in exit for.
A loop may add with name: identifier to its opening header. break identifier leaves that named enclosing loop; continue identifier resumes its iteration protocol. A post-test repeat keeps its trailing condition. Names do not bypass callable, concurrency or cleanup boundaries.
mark identifier names a statement destination without a colon or a new block. goto identifier supports forward and backward jumps to marks under scope, definite-assignment and cleanup rules. Marks and named loops are different targets. See the transfer specification.
Atomic multiword tokens use snake_case (for_each, old_value_of, add_handler). Compositional grammar stays separated (is not nothing, new Customer with).
Object extraction uses let from expression ... end let for a required source, or let_if from expression ... end let_if to assign nothing to every extracted local when the source is missing. Let_if is one keyword; from is a separate grammar word. Both evaluate the source once, and conditional extraction does not catch exceptions.
// starts a line comment; /// supplies documentation comments. /* ... */ block comments nest. An unclosed comment is an error. Comments can separate tokens but cannot splice two identifier fragments together.
Literals
| Form | Interpretation |
|---|---|
10, 0xFF, 0b1010, 0o755 |
Integral literal |
12.50 |
Decimal when not target-typed |
1e3 |
Double when not target-typed |
true, false |
Boolean |
"text", 'text' |
String |
"Hello {{name}}" |
Interpolated String |
raw "Hello {{name}}" |
Literal interpolation markers |
Numeric target typing precedes default inference. Untargeted integral inference selects Integer, then Long, then positive ULong where representable; otherwise report overflow. Ordinary C# numeric suffixes and digit separators are not DirectSharp literal syntax. Fractional literals require digits on both sides of the point.
Both quote styles delimit strings. Double the active quote to include it. Backslash is literal. A literal can satisfy a Char target when it represents one UTF-16 code unit; this is not a general runtime String-to-Char conversion.
let amount = 12.50
let path = "C:\Reports\invoice.csv"
let greeting = "Customer: {{customer.Name}}"
The last line is an action fragment with an existing customer. Its getter runs once; formatting failure propagates. nothing contributes empty text, and ordinary formatting uses current culture.
Reference C# for the first two declarations:
decimal amount = 12.50m;
string path = @"C:\Reports\invoice.csv";
Multiline strings use matching fences of at least three identical quotes. Fences must match in quote character and length. trim trims each line independently; raw controls interpolation. For line-ending policy and named control values, see text and Unicode.
The line breaks immediately inside the opening and closing fences are structural and excluded from the value. To include a leading or trailing blank content line, add one explicitly between the fence and the text. Further blank lines and indentation are preserved under the normal multiline rules; logical line breaks use Environment.NewLine.
Namespace roots, imports and aliases
A relative namespace is prefixed by the project root namespace. Global. escapes to the actual CLR/global root. Imports affect lookup; they do not copy types or change runtime behavior.
project
root_namespace Acme.Payments
import Global.System.Text.*
alias Json = Global.System.Text.Json.JsonSerializer
end project
There is one effective project declaration. Project.ds is the recommended filename; the declaration itself supplies semantics. A second project block is an error with both locations identified. Root namespace has one effective value; conflicting aliases are errors. File imports outside the project block remain file-local.
Terminal wildcard imports expand over known namespaces at compile time. There is no runtime assembly scanning or last-import-wins rule. Ambiguous imported symbols require qualification.
An alias denotes a symbol rather than an evaluated value. Aliasing a static property therefore reads that property on each use; use a local when a snapshot is intended. Calls and arithmetic expressions are not alias targets.
The DirectSharp root library namespace is automatically available by default; subnamespaces require explicit imports. auto_import_directsharp false inside project disables that automatic root import while preserving manual imports. Omission or explicit true keeps it enabled. Names and imports defines the complete binding rules.
Preprocessor directives
Typed definitions use #define NAME = value and #undef NAME. A conflicting definition is an error; deliberate replacement uses #define override NAME = value and requires an existing visible, replaceable target. Local changes do not mutate persistent solution/project configuration.
#define MODE = "production"
#define override MODE = 'test'
#include "shared/contracts.inc"
#include 'shared/helpers.inc'
Both quote styles are String delimiters here, as in ordinary source. The include examples require existing .inc fragments relative to the including file. Paths remain inside configured source/project boundaries; cycles are diagnosed with the include chain. Fragments preprocess recursively at the inclusion point and retain source locations. A computed runtime path expression is not an include operand. Chapter 25 defines conditionals, typed definitions and explicit substitution.
Source and literals
| Form | Meaning and constraints | Rules |
|---|---|---|
.ds; .inc |
Ordinary source; non-independent include fragment | CS-LEX-001 |
ASCII identifier; @identifier |
Case-insensitive, culture-independent identity; @ escapes keywords without changing identity |
CS-LEX-002, CS-LEX-007 |
//, ///, /* ... */ |
Line comment, documentation comment, nested block comment | CS-LEX-009 |
Newline; ; |
Normal separator; optional separator/terminator, including empty statement | CS-LEX-010–CS-LEX-011 |
0xFF, 0b1010, 0o755 |
Integer bases; leading zero alone is not octal | CS-LEX-012 |
12.50; 1e3 |
Untargeted Decimal; untargeted Double; target typing takes precedence | CS-TYPE-003 |
'text'; "text" |
String; double the active quote to include it; literal backslash | CS-LEX-015 |
{{expression}}; raw |
Interpolation; literal text without interpolation | CS-LEX-017–CS-LEX-018 |
Three or more matching quotes; trim |
Multiline fence; independent trimming of each line | CS-LEX-016, CS-LEX-019 |
See lexical structure for syntax, encoding, diagnostics and C# literal lowering. Ordinary C# numeric suffixes and digit separators are invalid. Interpolation evaluates once in source order, propagates exceptions, renders nothing as empty text, and uses current culture by default.
Canonical indivisible multiword tokens use snake_case (for_each, try_get); compositional forms keep spaces (is not nothing, end for_each). This is not permission to revive retired constructs.
Names and project declarations
| Form | Meaning | Rules |
|---|---|---|
Global.System.Guid |
Start at the actual CLR/global root | CS-NAME-001 |
project ... end project |
One effective project declaration; filename is conventional | CS-PROJ-001–CS-PROJ-002 |
root_namespace MyBank.Payments |
Prefix for relative source namespaces | CS-NAME-005 |
import Global.System.Text.* |
Terminal wildcard over known compile-time namespaces | CS-NAME-007 |
alias utcNow = Global.System.DateTime.UtcNow |
Symbol shortcut; each use reads the property | CS-NAME-008–CS-NAME-009 |
Only the DirectSharp root is automatically imported. Project imports/aliases apply project-wide; declarations outside the project block are file-local. Duplicate project declarations, conflicting aliases and ambiguous symbols are diagnosed. There is no last-declaration-wins rule.
A project disables the automatic import with auto_import_directsharp false inside project; omission or explicit true enables it. Manual imports remain valid. Binding and namespace examples are in chapter 3.