Z# (Zee-sharp)

A new .NET language

Meta Programming

Allow to write Z# code that executes at compile time in order to shift the workload as much as possible to the compiler.

.NET we can let the Z# compiler extract all the compile time code and generate an assembly behind the scenes during compilation. Then we can integrate calls to this assembly during further compilation.


Intrinsic Attributes

The compiler will allow accessing intrinsic attributes of the compiled code. These attributes are constants whose value was determined by the compiler at compile time.

A special operator is used to access them: #

Attribute Description
#type Full type info.
#typeid A unique numerical type identifier.
#name The identifier (name).
#min The minimum value possible.
#max The maximum value possible.
#bits The number of bits for the type.
#count The number of elements.
#size The size in bytes the type takes up in memory.
#default Default value for the type.
#mask Mask for retrieving a bit field value.
#offset Byte offset from the start of a structure to a field.
#expr Expression that was passed as a parameter (tracing)
#caller name of the caller of the function (tracing)

Not all types support all attributes. The compiler will give an error when the code accesses an attribute that is not supported by the type in question.

a = 42      // U8
a#size      // 1
a#bits      // 8
a#min       // 0
a#max       // 255
a#name      // 'a'

U8#size     // 1
U8#bits     // 8
U8#min      // 0
U8#max      // 255
U8#name     // 'U8'

Bit<3>#size // 1
Bit<3>#bits // 3
Bit<3>#min  // 0
Bit<3>#max  // 7
Bit<3>#name // 'Bit3'

Perhaps have a very short version of #$name (no space) for it will be used most often. (# = compile-time, $ = string)

a := 42
#$a              // 'a'
#$U8             // 'U8'
#$MyFunction     // 'MyFunction'

Pragmas

A pragma is a directive that instructs the compiler to take some action. For instance turn off a compiler warning temporarily.

A pragma is prefixed with: # that starts at the indent level of the current scope. It also starts a new scope.

TBD: A new scope is not always practical. How to make the dev choose?

Is the space after # mandatory or optional?

# ignore("CE3091")    // compiler fn call - starts a scope!
    code_that_causes_CE3091
    more_code_that_causes_CE3091
well_behaved_code

A # symbol not at the start of the current scope indent position, does not start a new scope.

A scope level ‘options’ pragma that executes all pragmas for that scope.

# push()
    enable(Checks.Bounds)
    enable(Checks.Overflow)

...     // rest of code file

At the end of the scope the options are poped automatically and previous settings are restored. Before the end of the scope the pop pragma can be used to restore the settings manually.

# pop() _

We can also use enable or disable that automatically push the previous state and a restore pragma to restore it afterwards.

Pragma Description
module Assigning code to a module
import Importing code from a module
export Making code public
push Pushing compiler configuration onto the (compile-time) stack
pop Popping compiler configuration from the (compile-time) stack
enable Enable a compiler feature (checks)
ignore Disable a compiler warning
align align structure
pack pack structure

TBD

Add a pragma that provides the compilation context and the project settings (think Visual Studio macros).

Project Setting Description
#project.targetPath Full path to the generated assembly.
#project.configuration Debug or Release

Compiler context are the current file, line, column, symbol etc functions?


Compile-Time Code

Any Z# code can be executed at compile-time. By placing a #! in front of the function, the compiler knows it is not to be included in the binary. The use of this symbol does not introduce an extra scope.

m = MyStruct
    ...

// this code can only run at compile time and is not included in the binary
#! compTimeFn: <T>(m: T)
    t = m#type
    t.name                      // 'MyStruct'
    loop f in t.fields
        "field: {f.name} of type {f.type.name}"

# compTimeFn(m)   // ok, call at compile time

// normal runtime function included in the binary
runtimeFn: <T>(m: T)
    ...

# runtimeFn(m) // call at compile time. Error if function body cannot be run at compile-time.
runtimeFn(m)      // call at runtime.

// alternate: use a #run pragma to run any code at compile time.
# run
    runTimeFn(m)    // can give compile error
    compTimeFn(m)   // run after previous

Some # compiler attributes may require the code to be #! compile time code. An example is the full #type information which is only available at compile time.

Not true anymore for .NET.


Compiler Functions

The compiler supplies a set of functions that allows interaction with- and modification of the generated code. There is also contextual information available for formatting diagnostic messages.

Function Note
line() the current source code line number
col() the current source code column number
file() the current source code file name
module() the current module the source code is part of
name() the name of current function or type being compiled
location() Fully formatted file/name/line/col text.
msg := "Error in '{#file()}' at line {#line()}: {#name()} is invalid."

What syntax/operator to use? # is a pragma and not a compiler function. Is there a difference between pragma’s and compile time functions? !# is compile-time execution (declaration) but the name could collide with custom functions. Using @ could be good alternative?

msg := "Error in '{@file()}' at line {@line()}: {@name()} is invalid."

Type Information

This is not the case now we target .NET. However I think type info for structs should still be restricted. Only interface types should be available.

No type information is available at runtime other than the #typeId which can only be used as type identifier to compare equality or for use as a key in a map/table store.

Full type information is only available at compile time. Are there any scenarios that would really become a problem not having type info at runtime?


Type Traits

A characteristic that defines a specific behavior.

A lot of the IsXxxx properties on the .NET Type class are traits.

fn: <T>()
    // special syntax?
    T::IsIntegral
    T::IsImmutable
    // traits as common template functions
    IsIntegral<T>()
    IsImmutable<T>()

// custom trait
#! IsTrait: <T>(): Bool
    ...

All traits are compile time functions. (You may want this at runtime?)


Hints to the compiler how to compile code… Syntax?

#inline     // pragma (hint)
@inline()   // compiler function
{inline}    // extension/decorator
[[inline]]  // extension/decorator
inlineFn: (p: U8): Bool -> p = 42

The expression body function syntax = could be used for inline functions…

Hint Description
inline duplicate function body at each call site (before optimization)
align x line struct up at a memory address that is a multiple of specified value

Use standard .NET code attributes for align? (I think those only work on .NET structs and not on classes)

TBD: allow call-site inlineing? b := #inline inlineFn(42)


TBD

Test for field exists

MyStruct
    field1: U8

s := MyStruct
    ...

// does 'field1' exist at compile time?
if s?#field1
    ...

// does 'field1' exist at runtime (dynamic)?
if s?field1
    ...

TBD

Are pragma’s, Compile-time functions and compiler functions all the same?

Pragma’s and compiler functions are the same and custom compile-time functions are simply a way to add ‘pragma’s’…?

Invocation always uses # to indicate the compile-time nature of the result (you can #-call runtime functions if they can be evaluated at compile time). Compile-time functions are declared with #! to indicate they should not end up in the binary. But that could also just be a #

Problem here is that some pragma’s start a new scope and others don’t. How to fix that?

TBD: Transparently pass custom meta programs to roslyn source generators?

TBD: Allow compile time code to build code using (perhaps) a builder pattern. TypeBuilder, FunctionBuilder and CodeBuilder.

TBD: How could we expose the type information on the to-be-compiled code to meta/compile time code?

TBD: Give compile time code access to the type info of source code and possibly extend the source code. The original code cannot be altered but it can be enhanced or extended. Pre and post processing of functions, wrapping of structs, implementing interfaces etc. See also Extensions. Also influencing type inferrence desicions the compiler makes for generic or template types.

TBD: #! = compile error, #? compile warning, #$ compile info.

if (compile-time-condition)
    #! Hey, this is not suppose to happen.