On this page

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.