Circle feature preview

Twitter: @seanbax

circlelang@gmail.com

The docs!

Circle reference and examples

Generating tensor contractions with Circle and TACO

Reverse-mode automatic differentiation with Circle and Apex

Circle video tutorial #1 - Serialization

Circle video tutorial #2 - Typed enums

RPN as an embedded Circle compiler

Parameter packs in Circle

Implementing a DSL using an open source dynamic PEG parser

Walkthrough 1: Injecting functions from text

Walkthrough 2: Evaluating expressions from text

Walkthrough 3: Deserializing JSON to classes

Type erasure with Circle

Pattern-matching expressions and enhanced structured bindings

Spaceship operator

F#-style type providers in Circle

Video - Circle compiler walkthrough

Compile-time regular expressions

The Circle format library

File @embed and a compile-time design dilemma

NEW List comprehensions, slices, ranges, for-expressions, functional folds and expansion expressions

NEW Dimensional analysis and SI units

The program!

For my pals, I'm making a preview build of Circle available.

Build 99 with future macros, @embed, extended slices, list comprehension and more!

Circle is experimental software. As you run into issues, please, pardon my bugs.

What is Circle?

Circle is a new programming language that extends C++ 17 to support data-driven imperative metaprogramming. Circle combines the immediacy and flexibility of a scripting language with the type system, performance and universality of C++. Three new features make this a more effective programming tool than Standard C++:

An integrated interpreter supports the execution of normal C++ statements at compile time. Same-language reflection programmatically allows for the generation of new code without involving an elaborate DOM or API for modelling the AST. Introspection keywords inform the program about the content of types, and expose data members and enumerators as iterable entities.

Circle accepts the C++ language as a starting point, and rotates that language from the runtime to the compile-time axis, allowing you to finally metaprogram C++ using C++.

Installation

The Linux build of Circle targets libstdc++, which is the C/C++ library and C++ ABI implementation maintained by the GCC project. You'll need a recent version of this library (Circle looks in /usr/include for the latest version) and the GNU linker ld to build programs with circle. From a clean Debian distribution, install these dependencies by running:

sudo apt update sudo apt install libstdc++-9-dev sudo apt install binutils

I use Linux Mint 19, which is based on Ubuntu 18.04. The compiler also works on the Ubuntu installation on Windows Subsystem for Linux. It's also been tested on Redhat (Fedora 30) and Arch Linux.

The Circle distributable is just an executable and sanity-check file in a tarball archive. You can install the compiler in any folder like this:

tar xvf build_99.tgz chmod +x circle

You'll want to run the included sanity check to confirm that libstdc++ installed correctly:

$ ./circle sanity.cxx Hello printf at compile time Hello cout at compile time $ ./sanity Hello printf at runtime Hello cout at runtime

Installing from Docker

If you're running an incompatible distribution, or a different OS altogether, you can install Circle in a Ubuntu image. Download this into an empty directory, change to that directory and build the Dockerfile and run the resulting image, as follows:

$ docker build --tag=circle_docker . $ docker run -it circle_docker bash # cd /home/circle_docs/examples/special # circle special.cxx Generating code for function 'E1' note: first Einstein special function Injecting expression 'sq(x) * exp(x) / sq(exp(x) - 1)' Injecting Taylor series Generating code for function 'factorial' Injecting statements 'double y = 1; while(x > 1) y *= x--; return y;' Generating code for function 'sigmoid' Injecting expression '1 / (1 + exp(-x))' Injecting Taylor series Generating code for function 'sin' Injecting expression 'sin(x)' Injecting Taylor series Generating code for function 'tanh' Injecting expression 'tanh(x)' Injecting Taylor series

The meta context

In compiled languages, we employ language features to guide execution of our program. Circle adds a new dimension of programmability, allowing you to program the compiler. Rather than introducing a complicated API for accessing front-end features (like a DOM for C++), C++ itself is used to guide C++ translation. The @meta keyword at the beginning of a statement causes that statement to be executed at source translation rather than at execution.

For example,

int main ( int argc, char ** argv) { printf ( " Hello world

" ); @meta printf ( " Hello circle

" ); }

$ circle hello.cxx Hello circle $ ./hello Hello world

During translation, normal statements are parsed, analyzed and inserted into the abstract syntax tree. During code generation, the abstract syntax tree is recursed and lowered into an intermediate representation like LLVM IR, and from that, an architecture-specific binary is generated.

In Circle, statements prefixed with the @meta token are parsed, analyzed and executed during translation (at definition or at instantiation in the case of template-dependent contexts). Because statements may be executed at compile time, the package includes an interpreter with the ability execute C++ AST and make foreign function calls (in this example, to printf in libc.so ).

Why is this so impactful? We can guide source translation by employing compile-time control flow with data loaded at compile time by the interpreter.

The Circle language consists of three primary feature domains:

Integrated interpreter Same-language reflection Introspection keywords

Combine these features to design your own metaprogramming idioms.

As a trivial example, consider listing the types for each data member of a structure in a plain text file in the source directory. We'll use ordinary C library functions to open the file and read out its contents, line-by-line, during source translation. For each type in the file, we'll declare a data member in the structure foo_t and name them _0 , _1 and so on.

We'll use Circle's introspection keywords as a sanity check. print_struct iterates over a class's data members and prints the type and name of each of them.

struct.fields

int double char*

fields.cxx

# include < cstdio > # include < cstdlib > struct foo_t { // Load struct.fields from the source folder. @meta FILE * f = fopen( " struct.fields " , " r " ); // Declare one member of foo_t per line in the file. // @type_id converts a string (known at compile time) to a type. // @(XX) converts an integral expression XX to an identifier of the // form _XX. // Since the body of the loop is not a meta statement, it falls out of the // loop (which is a meta scope) and into the enclosing non-meta scope, which // is the struct definition, where it is translated as a member-specifier. @meta char line[ 256 ]; @meta for ( int i = 0 ; fgets(line, 256 , f); ++i) @type_id(line) @(i); // Be kind, rewind. @meta fclose (f); }; // Loop over each non-static data member in the parameter type to convince // ourselves that the trick above worked. template < typename type_t > void print_struct () { printf ( " %s

" , @ type_name ( type_t )); @meta for ( int i = 0 ; i < @ member_count ( type_t ); ++i) printf ( " %s %s

" , @ type_name (@ member_type ( type_t , i)), // print the type of the member @ member_name ( type_t , i) // print the name of the member ); } int main ( int argc, char ** argv) { // Print all the fields in foo_t: print_struct< foo_t >(); return 0 ; }

$ circle fields.cxx $ ./fields foo_t int _0 double _1 char* _2

The white paper and examples repository has deeper examples, and focuses on a "configuration-oriented programming" paradigm and on improving C++ template metaprogramming.

Usage

Like any compiler, --help lists options. Start with that.

Clone the white paper and examples project to get started with the examples.

Specify the Circle/C++ file as the single positional argument. circle does not currently serve as an indepedent linker, so if you have multiple translation units, you can:

With one pass per file, compile to .o and use clang to link.

With one pass per file, compile to .bc, use llvm-link-8 to link the bitcodes, llc-8 to optimize and generate a .o file, and clang to link. This provides inter-procedural optimization across translation units. For single translation unit programs, circle will link to executables or shared objects as specified by the -filetype argument or inferred from the output filename extension.

Building with Circle

Circle adopts the same general command-line arguments practiced by gcc and clang:

-S emits an ASM file (.s).

emits an ASM file (.s). -S -emit-llvm emits a human-readable LLVM IR file (.ll).

emits a human-readable LLVM IR file (.ll). -c -emit-llvm emits a binary LLVM IR bitcode file (.bc).

emits a binary LLVM IR bitcode file (.bc). -c only compiles to an object file, without linking.

only compiles to an object file, without linking. -console prints the output (if text) to the terminal. This is very useful when paired with -S or -S -emit-llvm

prints the output (if text) to the terminal. This is very useful when paired with or -O0, -O1, -O2 and -O3 are the usual optimization levels.

are the usual optimization levels. -g generates debug info.

generates debug info. -M names a compile-time shared object library. This enables compile-time foreign function calls to compiled code.

names a compile-time shared object library. This enables compile-time foreign function calls to compiled code. -stat reports on files opened and lines lexed.

reports on files opened and lines lexed. -no-demangle prints mangled names in link errors.

Paths

Circle searches some common paths to establish default system header and meta library paths. Use --print-paths to show which default paths it uses on your installation.

The gcc-style command-line arguments -isystem , -I and -iquote add #include paths with different precedences to the preprocessor. Additionally, in Circle, the -M command-line argument adds shared object files to be loaded by the interpreter to support the foreign-function calls to non-standard libraries. For convenience, the compiler also checks the files circle.isystem , circle.I , circle.iquote and circle.M in the working directory--if one of those files isn't found, it checks the compiler's directory for the same file. These files contain the header and library paths in plain text, typed out one per line.

The gcc-style -D command-line argument supports defining simple preprocessor macros at invocation. Additionally, the file circle.D is opened (first from the working directory, and if not found there, from the compiler's directory), and its contents are injected verbatim into the top of the translation unit. To clarify, -DVERSION=5 injects an object-like macro VERSION set to 5. A file circle.D containing the text #define VERSION 5 achieves the same thing. The circle.D feature is more flexible than the command-line argument, as it allows you to define function-like macros in addition to object-like macros.

License