Giter Site home page Giter Site logo

cx-language / cx Goto Github PK

View Code? Open in Web Editor NEW
126.0 7.0 9.0 5.84 MB

C* is a hybrid low-level/high-level systems programming language focused on performance and productivity.

Home Page: https://cx-language.github.io/

License: MIT License

CMake 0.78% C++ 74.41% Shell 0.17% C 0.16% Python 0.45% LLVM 24.04%
programming-language compiler systems-language language native high-performance system-programming performance fast

cx's Introduction

The C* Programming Language

C* (pronounced "C star") is a C-based hybrid low-level/high-level programming language focused on runtime performance and developer productivity (in this order of priority). The language is simple and unopinionated, supporting imperative, generic, data-oriented, functional, and object-oriented programming.

Learn more about the language at https://cx-language.github.io/.

The project is still in early stages of development: some planned features have not yet been implemented, existing ones are subject to change, and the documentation is scarce.

Contributing

Contributions are welcome. See the GitHub issues and the Trello board for the project backlog. To ask a question or open a discussion, create an issue or join the C* Discord server.

Building from source

Compiling C* requires a C++17 compiler, CMake, and LLVM/Clang 12.

To run the tests, install lit and its optional dependencies via pip:

sudo python3 -m pip install lit psutil

After this, the following commands can be invoked from the build directory:

  • cmake --build . builds the project.
  • cmake --build . --target check runs the test suite.

The C++ code style is enforced by ClangFormat and ClangTidy:

  • Run the format CMake target to format the code.
  • Run the lint CMake target to check if the code is formatted.

License

C* is licensed under the MIT license, a permissive free software license. See the file LICENSE.txt for the full license text.

cx's People

Contributors

coolnesss avatar emlai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cx's Issues

support for musl libc

Musl libc doesn't have libexecinfo support which is why there is error at compile time:

/tmp/delta-991067.o: In function printStackTrace': :(.text+0x2146): undefined reference to backtrace'
:(.text+0x2154): undefined reference to `backtrace_symbols'
collect2: error: ld returned 1 exit status

I've disabled[1] some lines in stdlib/os/gnu.delta by commenting some lines and compiling works on musl libc. I was wondering if it would be better to have support for both glibc or musl by ifdefs or something like that?

1:

//extern func backtrace(array: mutable void mutable**, size: int) -> int;
//extern func backtrace_symbols(array: mutable void**, size: int) -> mutable char mutable**?;

func printStackTrace() {
//var callstack: mutable void*[128] = undefined;

//let frames = backtrace(&callstack[0], int(callstack.size()));
//let strings = backtrace_symbols(&callstack[0], frames);

//if (strings == null) {
//    return;
//}

//for (let i in 0..frames) {
//    printf("%s\n", *(strings! + i));
//}

//free(cast<mutable void*>(strings!));

}

func setAbortBehavior() {

pointer arithmetic

I tried to modify example for pointers, to:

void main() {
    int i = 6;
    int* p = &i; 
    ++p; ++p; p++; // what would this do?
    println(p); 
}

I expected it not to compile, to crash, or print some nonsense, but got 9. It feels nonintuitive, that invisible dereference.

cmake fails, build failed.

@Coolnesss @emlai

I installed everything and llvm from releases to /Desktop/mymac/llvm and cmake returns this:

cmake .. -DCMAKE_PREFIX_PATH="/Users/mymac/Desktop/llvm"

CMake Error at CMakeLists.txt:49 (find_package):
  Could not find a configuration file for package "LLVM" that is compatible
  with requested version "9.0".

  The following configuration files were considered but not accepted:

    /Users/mymac/Desktop/llvm/lib/cmake/llvm/LLVMConfig.cmake, version: 10.0.0
    /Users/mymac/Desktop/llvm/lib/cmake/llvm/llvm-config.cmake, version: unknown



-- Configuring incomplete, errors occurred!
See also "/Users/mymac/Desktop/delta-master/build/CMakeFiles/CMakeOutput.log".

OS: macOS x86_64

What should I do to fix this?

Why delta instead of C++/Rust?

Adding a short explanation to the docs about why one would use delta instead of C++/Rust, should be considered. At this moment it only tells us, that delta should be seen as a replacement for C++, but what does it do better then?

Initialize Array with array literal

E.g

var a = Array([1,2,3,4]);

Note also no generics type needed, since it can be determined from the type of the array given to the constructor

Block comment ignores strings

This failed:

/*
void main() {
    println("Hello /* world"); // <<< open block comment in a string
}
*/

void main() {
    println("Hello world");
}

with error:

main.delta:1:1: error: unterminated block comment
/*
^

I noticed, that in file parser/lex.cpp the function Lexer::readBlockComment doesn't handle strings.

Private and public members

Now that the standard library is growing, we really need this.

Suggestion: could be e.g

class Foo {
   // private stuff
   public:
   // public stuff
   protected:
   // package-only stuff
}

Which would mean everything is private unless explicitly stated otherwise

documentation examples that throws error

I stumbled upon some issues with the examples in the documentation. There errors are found in the function section:

main.cx:1:16: error: expected ',', got '='
void foo(int i = 0) {
^

main.cx:6:9: error: expected identifier, got '('
var (i, b) = foo();
^

I get error with array reference example as well:

Assertion failed: left->getType()->equals(right->getType()) (/ev/.cx/src/backend/irgen.h: createBinaryOp: 146)
Please submit a bug report to https://github.com/cx-language/cx/issues and include the crash backtrace.
Stack dump:
0. Program arguments: cx array.cx
zsh: abort cx array.cx

Map, filter example htrows this error:

array.cx:8:31: error: invalid argument #1 type 'bool(int)' to 'List.filter', expected 'bool(int*)'
var even = numbers.filter(isEven);
^

Pointers example gives this error:

pointers.cx:14:15: error: unknown identifier '*'
println(p * q); // Multiplies the values pointed to by p and q, prints 42
^

Nullable types has these errors:

nullable.cx:17:23: error: no matching operator '+' with arguments 'int?' and 'int'
        int bar = foo + 1;
                      ^
/ev/.cx/std/StringBuffer.cx:199:14: note: candidate function:
StringBuffer operator+(string a, string b) {
             ^
/ev/.cx/std/StringBuffer.cx:213:14: note: candidate function:
StringBuffer operator+(string a, char b) {
             ^
/ev/.cx/std/StringBuffer.cx:217:14: note: candidate function:
StringBuffer operator+(string a, char[*] b) {
             ^
/ev/.cx/std/StringBuffer.cx:221:14: note: candidate function:
StringBuffer operator+(StringBuffer a, string b) {
             ^
/ev/.cx/std/StringBuffer.cx:231:14: note: candidate function:
StringBuffer operator+(StringBuffer a, char b) {
             ^
/ev/.cx/std/StringBuffer.cx:237:14: note: candidate function:
StringBuffer operator+(StringBuffer a, char[*] b) {
             ^

Non-null assertion operator:

Assertion failed: Val && "isa<> used on a null pointer" (/usr/include/llvm/Support/Casting.h: doit: 104)
Please submit a bug report to https://github.com/cx-language/cx/issues and include the crash backtrace.
Stack dump:
0. Program arguments: cx nullable.cx
zsh: abort cx nullable.cx

Statement-level conditional compilation

Conditional compilation blocks can currently be used only at global scope. Allow using them also inside functions.

This will probably be implemented via compile-time if-statements.

LLVM ERROR: inconsistency in registered CommandLine options / undefined reference to `backtrace'

hello I love the idea of this programming language and I would like to try it out, right now I am running on llvm 12 and I managed to set CMakeLists.txt to search for newer version of llvm and compiled it successfully. However it gives me error when running the cx executable. Something to do with invalid arguments:

: CommandLine Error: Option 'debug-pass' registered more than once!
LLVM ERROR: inconsistency in registered CommandLine options
zsh: abort ./cx

File I/O

Class / etc to support easy reading and writing of files. Both binary and textual, as well as console I/O

  • implement file reading (#31)
  • implement file writing (#32)

Enum enhancements

  • enum member functions
  • allow enums to implement interfaces
  • allow omitting enum type when it can be inferred

cannot call mutating function with immutable 'this'

I get

./src/Vector.delta:17:24: error: cannot call mutating function with immutable 'this'
      array.append(*arr[e]);
                       ^

for

init(arr : Vector<T>&) {
    this.array = Array<T>();

    for (e in 0..array.size()) {
      array.append(*arr[e]);
    }
  }

Even though this should be mutable in the constructor

Question: What is relation between this language and the one mentioned on Wikipedia?

I'm one of the maintainers of the Sample Programs, and I've been trying to find or create docker images for the different languages that are currently untested. I stumbled upon this repo and went to the trouble of creating a docker image. Here is the Dockerfile if you're curious.

While looking for information on this language, I saw this Wikipedia Article and found this User Guide from the now defunct company Thinking Machines. I'm just curious if this is in any way based on that version of C* or if it is a completely new language that just happens to have the same name. The reason why I'm asking is that I'd like to update these articles on the Sample Programs companion website as a result of this PR:

Non-pointer optional types

Currently only pointer types can be declared optional:

var a: int*    // non-optional, cannot be null
var b: int*?   // optional, can be null

Extend this feature also to other builtin types and structs, with the same postfix-? syntax.

This could be implemented using tagged unions once we have them, see #26.

Testing support

Here's long list of what thorough testing support could be. It is more tooling support than language feature.


1] It should be really easy to write a new test.

TEST()
{
  assert(2 + 2 == 4);
}

TEST()
{
  assert(2 + 2 == 5);
}

The main point is: one doesn't need to invent a unique name for a test.

The usual alternatives:

@test
void foo()
{
  assert(2 + 2 == 4);
}

or:

void test_foo()
{
  assert(2 = 2 == 5);
}

give false perception that test is a function (it is not, no one calls a test). Inventing a name is hard in large programs, IDE browser may be cluttered with these useless names.

Uppercase TEST makes it easy to spot.


2] Tests may have optional parameters. For example, maximum expected time for the test. If it takes longer, there's something wrong, error could be shown.

Test parameters are not like ordinary function parameters, they should be free form. E.g.:

TEST() // no parameters
{
}

TEST(time < 10 ms) // one parameter
{
  ...
}

TEST(10 ms  < time  < 30 ms) // still only one parameter
{
   ...
}

// two parameters: a name if we like to call the test individually, max time, plus some separator
TEST(name = "xyz" |  time < 1 s) 
{
   ...
}

// three parameters
TEST( qwerty | asdf | x = true)
{
  ...
}

There should be several implicit parameters available:

  • something like __FILE__/__LINE__ for every test
  • how long ago (e.g. in seconds) was the source file with current test modified. Useful when running only tests from recently modified source files.

3] The last component of test system is the "test runner". It collects all tests, reads their parameters (if any), parses these parameters (giving error if it doesn't understand) and then executes all or some of the tests.

There should be default test runner, which understands couple of the most common parameters (name, timeout, ...).

Its API could be:

// returns # of executed tests, 
uint run-all-tests(bool show_results_dialog_on_success);
uint run-recent-tests(uint recent_in_seconds, bool show_results_dialog_on_success);

The default test runner should be able to verify timeout constrains, if present. A default timeout could be e.g. 1. second.


4] Custom test runner may be used e.g. to run those tests used for performance regressions. These tests would be identified by a parameter, their duration would be stored somewhere, and if it gets worse, programmer would be notified.

Another use case for a test runner is code coverage. Certain tests could be labeled so, and run only when code coverage is needed (because they would take lot of time).

Another use case is to exclude platform specific tests.


5] It should be up to the programmer, to invoke the test runner at the beginning of the application. Either the default one (e.g. run-recent-tests), or a custom variant.

There should be no special "test invocation mode" for the compiler. It is hassle, it complicates things, it is inflexible. Just let the programmer do it explicitly in main, exactly as he wishes.


6] Where should be tests placed? There are several options:

  • after the relevant code (e.g. after a function)
  • at the end of source file (this one I do not recommend, gets messy very fast)
  • in a separate file (not to overcrowd the source file)

IMO programmer should have choice. Most important small tests could be placed after the functions, those long and checking minutae details could be put into the separate file. Let's call it companion test file.

Companion test file should have name similar to its "parent source file", should have full access to it, w/o need to import anything. It should behave as if it was copy-pasted at the end of the source file.

Companion test file should define only tests (plus maybe helpers), should not export anything, should not be importable elsewhere. It should be just a convenient storage for tests, nothing else.


7] Tests should have access to everything. Nothing should be private to them.


8] Since the language plans to have generics, there could be tests for uncompilable code:

// this parameter say the code inside test must NOT compile
TEST(does-not-compile) 
{
   ...

}

Parameter does-not-compile cannot be used with any other parameter together.

The compiler should make sure the code really doesn't compile, but NOT because of some trivial error, like unbalanced parenthesis or an invalid name.


9] If possible, test should be able to use any part of the project, w/o explicit need to import anything. This is keep the code clean, not to pollute it it because of the tests. If all tests are removed, the rest of the code should not need any modification.

E.g.

TEST()
{
  // this should not require explicit import, if compiler is able to infer what it is
  SomethingFromThisProject x;   
  assert(foo() == x);
}

I'm not sure whether this is feasible, but it would help a lot.


10] With usual compilation modes debug and release, it should be possible to use unit test both in debug and in release too (e.g. for performance checks, and to make sure optimization didn't screw up something).


11] All tests should do memory checking. Whatever was allocated within certain test, should be also deallocated right there. I'd implemented such feature in C and C++ testing library, and it does wonders to keep application free of leaks.

I know there are big complicated tools that try to do the same, but having such tool inside every test allows to identify leaks very quickly.

Such checking against the leaks could be major "selling point" for the language.

Intentionally leaking tests could be annotated with a parameter.


12] assert could be seen as yet another tool, not just as a function:

When this fails:

assert(x == y)

you may be interested what the values of x and y were. Advanced assert could help:

assert(x == y | x = %x y = %y, z = %z); // z is an interesting value visible in this scope

Here, if assert fails, it would print x, y and z values. The syntax of assert should not be restricted by language rules. Whatever helps should be available. E.g. I could imagine something like:

assert(x == y) 
{ // code block accompanying the assert
  a = x.foo();
  b =y.bar();
  assert_print(Because x has %a and y has %b it failed here);
}

This feature would help a lot against Heisenbugs.


13] When assert is fired while a test is running, it should show the exact location of the test (it should not be hidden inside a nearly useless long stack trace).


14] There are different kinds of assert.

  1. The good old ordinary assert. Should be used a lot, should be active in debug mode, should be compiled away in release mode. Nothing unusual here.

  2. The assert used in a test, at the top level of such test:

    TEST()
    {
      assert(2 + 2 == 4);
    }
    
    

    If test are allowed in release mode, then such asserts should not be compiled away. There are two options:
    a) use different name (e.g. verify). It would be the same as assert, but allowed only in tests, at the top level, nowhere else. It would be present even in release mode.
    b) compiler should keep asserts in tests (at the top level) intact, even in release mode

  1. There could be asserts "just to be sure", placed to notify the user that something impossible did actually happen. E.g.:

       if (something-impossible) {
          assert(false); // cannot happen
          return -1;
       }
    

    However, when one does truly defensive testing, such situations should be arranged and tested.
    But how to distinguish the situation when we intentionally provoked this situation versus the unexpected error? There are two options:

    a) If assert(false) is fired, it would check whether a test is running. If it is, then it would assume it was triggered intentionally, and it would not show the error. If tests is not running, impossible bug was spotted, show it.
    b) there could be special form of assert, e.g. impossible_assert(). If invoked during a test, it does nothing, outside of a test it shows error.


15] The language should standardize how to distinguish between debug and release modes, between tests compiled in and tests not present. It should avoid the C/C++ mess with NDEBUG, DEBUG, _DEBUG and other inconsistent inventions.


16] Mocking support. The biggest feature I can imagine. Ability to replace specified functions for the duration of the test:

TEST()
{
  mock fopen = ... // fopen dummy reimplementation
  mock fclose = ...

   // code doing lot of fopen/fclose, but using mocks instead.

}

It could be implemented using function pointers. In release mode without test there would be no performance penalty at all.


17] Mocking support could be extended even to constants. E.g. timeout constant, to keep test duration down.

It could be also implemented using function pointers. The constant would became variable accessed by calling that function pointer.


18] Support for white box testing. Another potentially huge feature.

The problem: I want to be sure that this code invoked exactly 2 allocations and 2 deallocations and does NOT invoke any TCP/IP calls.

This could be handled by making the code very complicated, or by using "tracing" feature proposed here:
http://akkartik.name/post/tracing-tests

Basically, it could work this way:

  1. Within a test you specify your intent, to watch for certain "traces". Here it would be "allocate", "deallocate", "socket", etc.
  2. If such a trace happens (either it is logged explicitly somewhere in the code, or implicitly, e.g. a funtion call), then such a trace would be stored somewhere.
  3. When the test ends, it goes through stored traces. It makes sure there are 2 "allocate", 2 "deallocate", no "socket" etc. It could check their proper order, it could check parameters (like bytes allocated).

E.g.

TEST()
{
  // I 'm interested in these calls - record them
  register-trace("allocate", "deallocate", "socket"); 

   ...
   ... code to be tested
   ...

  // now I do check whether my expectations were correct
  assert(check-for-trace("socket") == false); // no TCP/IP?

  // 2 allocations and then 2 deallocations?
  assert(check-for-trace("allocate"));
  assert(check-for-trace("allocate"));
  assert(check-for-trace("allocate") == false); // not 3

  assert(check-for-trace("deallocate"));
  assert(check-for-trace("deallocate"));
  assert(check-for-trace("deallocate") == false);

  // all collected traces would be wiped out the end of the test
}

A trace could be created explicitly:

void* allocate(uint bytes)
{
   ...
   TRACE("allocated %u bytes", bytes);
   return p;
}

or implicitly, by the compiler inserting trace when a function is called. The compiler would know (by checking the "register-trace" within all tests) which functions could be possibly checked and which do not.

void* allocate(uint bytes)
{
   ...
   TRACE("allocate()"); // inserted implicitly by the compiler
   ...
}

Implicit traces reduce clutter in the code.

The compiler should check the traced strings, to make sure there's no typo, should really do the implicit traces (to avoid unneeded clutter in the code).

The whole mechanism should be well optimized not to slow down the tests too much and to optimize trace data.

When tests are not running, no traces would be collected. There would be only small impact on performance.

When tests are not compiled in, there would be no traces and no impact at all on the performance.


19] Tests should run strictly serially. Parallel execution is virtually impossible (every single piece of code would need to be guarded against multithreading bugs). If something multithreaded has to be tested, a test should spawn such threads.

Running only recent tests automatically whenever program is started take little time and guards against bugs well.

I routinely do:

int main(void) {
#ifdef DEBUG
  (void)run-recent-tests(
    recent_in_seconds: 60 * 60 /* hour*/, 
    show_results_dialog_on_success: false);
#endif
   ...

If someone really, really needs parallel execution of tests, he could write his own test runner and blame himself for the problems.


20] Having a failing test and then running remaining ones should not be supported. Its a misfeature.

If someone really, really needs this, he could write his own test runner, plus probably overload the assert.


21] There could be support to also invoke tests from foreign C code, or to allow invocation of the tests from C code. This feels to me as a trivial thing.


22] Tests are expected to run at the start of main. However, nothing stops one to invoke some of the tests at any moment, even interactively. This may help to deal with Heisenbugs.

Tests checking automatically against leaks would make this adventure less risky.

Multithreaded applications would need to protect the tests invoked in the middle of application run, but this is unavoidable.


23] People who do not like testing wouldn't be forced to use them, those who want their own unique testing framework could implement one. Ability to overload or overwrite assert would be desirable in this case.


AFAIK no language known to me supports all the features, not even half of them. Most only pay lip service to testing, few stopped in the middle (e.g. language D allows easy definition of tests, but neither test parameters not custom test runners).

Some of proposed features were implemented in C++ and even in C. It is possible to write:

#ifdef DEBUG
TEST()
{
  assert(2 + 2 == 4);
}

TEST()
{
  assert(2 + 2 == 5);
}
#endif

It was also possible to implement whitebox testing too, but w/o compiler support it gets too clumsy.

type T not converted automatically

I've got

class Matrix<T> {
  init(array : T[]&) {
    // ...
  }
}

and

// 
let a = [1,2,3];
var mat = Matrix<int>(&a);

Which gives

./src/main.delta:6:27: error: invalid argument #1 type 'int[3]&' to 'Matrix', expected 'T[]&'
    var mat = Matrix<int>(&a);
                          ^

Build failure on Fedora 35

On Fedora 35 Workstation (x86_64), the build fails as follows:

% cmake --build .
[ 12%] Building CXX object CMakeFiles/FileCheck.dir/Unity/unity_0_cxx.cxx.o
[ 25%] Linking CXX executable FileCheck
[ 25%] Built target FileCheck
[ 37%] Building CXX object CMakeFiles/cx.dir/cmake_pch.hxx.gch
[ 50%] Building CXX object CMakeFiles/cx.dir/Unity/unity_3_cxx.cxx.o
[ 62%] Building CXX object CMakeFiles/cx.dir/Unity/unity_2_cxx.cxx.o
[ 75%] Building CXX object CMakeFiles/cx.dir/Unity/unity_1_cxx.cxx.o
[ 87%] Building CXX object CMakeFiles/cx.dir/Unity/unity_0_cxx.cxx.o
[100%] Linking CXX executable cx
/usr/bin/ld: cannot find -lclangAST
/usr/bin/ld: cannot find -lclangBasic
/usr/bin/ld: cannot find -lclangFrontend
/usr/bin/ld: cannot find -lclangLex
/usr/bin/ld: cannot find -lclangParse
/usr/bin/ld: cannot find -lclangSema
collect2: error: ld returned 1 exit status
gmake[2]: *** [CMakeFiles/cx.dir/build.make:170: cx] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:304: CMakeFiles/cx.dir/all] Error 2
gmake: *** [Makefile:101: all] Error 2
cmake --build .: 45.30s user, 2.89s kernel, 1253M memory
%

The build succeeded with this patch to CMakeLists.txt:

  diff --git a/CMakeLists.txt b/CMakeLists.txt
  index c50b6b4d..a62983aa 100644
  --- a/CMakeLists.txt
  +++ b/CMakeLists.txt
  @@ -71,7 +71,8 @@ if (LLVM_LINK_LLVM_DYLIB)
   else()
       llvm_map_components_to_libnames(LLVM_LIBS core native linker support)
   endif()
  -list(APPEND LLVM_LIBS clangAST clangBasic clangFrontend clangLex clangParse clangSema)
  +# list(APPEND LLVM_LIBS clangAST clangBasic clangFrontend clangLex clangParse clangSema)
  +list(APPEND LLVM_LIBS clang-cpp)
   target_link_libraries(cx ${LLVM_LIBS})
   
   add_custom_target(check_lit COMMAND lit --verbose --succinct --incremental ${EXTRA_LIT_FLAGS} ${PROJECT_SOURCE_DIR}/test

Windows support

  • Check that the compiler builds on Windows with at least Visual Studio.
  • Make the Delta standard library compile on Windows.

Preprocessor

Few notes about the preprocessor, mainly based on the short mention in docs.

  1. Preprocessor has one very useful property. It runs the first, always. As such, it is easy to think about it, unlike actions done on the source at some unknown point later.

  1. Docs: Delta only has the conditional compilation directive #ifdef and its friends, which can be configured only from the command-line, not from source code as can be done in C and C++ with #define.

    This is going in the good direction, but I'd extend it for large scale programs:

    1. If program is small, then having its defines on cmdline is sufficient.

    2. However, if the code base gets bigger, it may be inconvenient. Cmdline length has its limitations and it's hard to place cmdline parameters into versioning system.

      My proposal is for such projects to use auxiliary file, with fixed known name (e.g. configuration.delta, always in project root, the name being reserved and not allowed for ordinary source). This file would contains all defines for the project and nothing else. If command line still uses any defines, they would apply only to this auxiliary file, not elsewhere. (This means the cmdline defines would be valid only in configuration.delta, and may not be used at all in the ordinary code).

    This arrangement would keep all defines in one, well known and easy to review location.

    A 3rd party library could provide their own configuration.delta file, intended to be a hint which specified what macros the library uses. Its content could be manually merged into the project's configuration.delta in the project's root. Library's configuration.delta would be ignored by the compiler (due to being in "wrong" place).


  1. "Boolean" defines, e.g. #define FOO (-DFOO), should be always present even if undefined/false.

     #define FOO // goes the path1 : #ifdef FOO path1 #else path2 #endif
    
     #undef FOO // goes path2 : #ifdef FOO path1 #else path2 #endif
    
     ... if FOO is neither defined not undefined, its use means error
    
    

    On cmdline it could be -DFOO, -UDFOO, or -DFOO=false.

    This would eliminate oversights, when someone forgot something and quietly and unknowingly picked up the undef path.


  1. If define is numeric, then it would be helpful to provide range or enumeration for the value:

    #define FOO_PERCENTS  40 in 0 .. 100
    #define SIZE 1 in 1, 2, 4, 8, 16
    

    These numeric constraints would be observed in the code, e.g. #if FOO_PERCENTS > 200 would be then refused as completely impossible.

    This would eliminate basic mistakes, using some number while not really knowing what it means.


  1. If define is enumeration of symbols, then all symbols should be present, like:

    #define PLATFORM WIN in WIN, UNIX, MACOS
    

    Use of enumerated define would check against typos, against unknown values and against missing values. Newly added platform would need the proper update everywhere, it won't leave some path forgotten.

    All enumerated values should be covered:

    // this would be OK
    #if PLATFORM == WIN ... #elif PLATFORM == UNIX .... #elif PLATFORM == MACOS .... #endif 
     
    // here compiler would complain about missing path for MACOS
    #if PLATFORM == WIN ... #elif PLATFORM == UNIX .... #endif 
    

  1. I use this trick:

    #ifdef DEBUG
    #  define DBG(...) __VA_ARGS__
    #else
    #  define DBG(...) 
    #endif
    

    to have debug only function parameters and values w/o much typing. It is technically macro substitution, but not really. Something like that would be handy.

    C example:

    // in DEBUG mode function takes additional __FILE__, __LINE__ 
    // parameters for better troubleshooting
    void foo(int x, int y DBG(, const char* file, int line)) { ... }
    ...
    foo(10, 20 DBG(, __FILE__, __LINE__));
    

    Ideally, the language would be aware what such trick means, and would NOT require that noisy comma separator:

    // comma inserted automagically
    void foo(int x, int y DBG(const char* file, int line)) { ... }
    ...
    foo(10, 20 DBG(__FILE__, __LINE__)); // comma inserted automagically
    

    or perhaps even would insert "implicit DBG macro":

    void foo(int x, int y DBG(const char* file, int line)) { ... }
    ...
    foo(10, 20,  __FILE__, __LINE__); // means foo(10, 20) in release
    

Repl

Make repl fully functional

  • allow statements
  • allow declarations
  • allow using stdlib
  • more...?

Autogenerated initializers

Defining no initializers for a struct should cause a default initializer to be generated that initializes all members from parameters, e.g.:

struct S {
  var i: int;
  var b: bool;
}

should generate:

init(i: int, b: bool) {
  this.i = i;
  this.b = b;
}

Update documentation

To include all language features and more examples

Concepts to cover in the Book:

  • everything from spec.md
  • package manager
  • build system

Passing generic parameters if they aren't defined for the class

e.g

class Foo {
  // ...
}

var lol = Foo<int>();

Gives an error

delta: ../src/ast/decl.cpp:69: delta::Type delta::TypeDecl::getType(llvm::ArrayRef<delta::Type>, bool) const: Assertion `genericArgs.size() == genericParams.size()' failed.

but should give a proper error

Pip does not come native with MAC OS.

I tried to run setup-build-macos script, but I think that pip does not come native with mac os.

+ sudo pip2 install lit
sudo: pip2: command not found

I solved problem by running sudo easy_install pip. You might want put this to script or give some error message like you do with brew.

Add more examples

Here are a few examples generated by ChatGPT:

// Import the standard math library.
import "math.h";

// Define a generic function that calculates the average of two values.
T average<T>(T a, T b) {
    return (a + b) / 2;
}

// Define a struct that represents a 2D point.
struct Point {
    x: f32;
    y: f32;
}

// Define a function that calculates the distance between two points.
f32 distance(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

// Define a class that represents a circle.
class Circle {
    // Define a private field to store the circle's radius.
    private radius: f32;

    // Define a constructor that initializes the circle's radius.
    constructor(radius: f32) {
        this.radius = radius;
    }

    // Define a method that calculates the area of the circle.
    area(): f32 {
        return this.radius * this.radius * PI;
    }

    // Define a method that calculates the circumference of the circle.
    circumference(): f32 {
        return 2 * this.radius * PI;
    }
}

void main() {
    // Use the `average` function to calculate the average of two numbers.
    var a = average(3, 7);
    var b = average(5.5, 8.5);

    // Print the results of the averages to the console.
    println(a); // 5
    println(b); // 7

    // Create two points and calculate their distance.
    var p1 = Point { x: 0.0, y: 0.0 };
    var p2 = Point { x: 3.0, y: 4.0 };
    var d = distance(p1, p2);

    // Print the distance to the console.
    println(d); // 5

    // Create a circle with radius 2.0 and calculate its area and circumference.
    var circle = Circle(2.0);
    var area = circle.area();
    var circumference = circle.circumference();

    // Print the area and circumference to the console.
    println(area);         // 12.566371
    println(circumference); // 12.566371
}
// Import the standard input/output library.
import "stdio.h";

// Define a function that calculates the factorial of a number.
int factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

// Define a function that checks if a number is prime.
bool isPrime(int n) {
    if (n < 2) {
        return false;
    }
    for (var i = 2; i < n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

void main() {
    // Read a number from the user.
    print("Enter a number: ");
    var n = 0;
    scanf("%d", &n);

    // Calculate and print the factorial of the number.
    var fact = factorial(n);
    printf("%d! = %d\n", n, fact);

    // Check if the number is prime and print the result.
    var prime = isPrime(n);
    printf("%d is%s prime.\n", n, prime ? "" : " not");
}

Lambda expressions

  • MVP: non-capturing lambda, explicit parameter types, single-expression body (a08e68e)
  • implicit parameter types
  • allow block as body
  • allow capturing variables from enclosing scope

Allow iteration of builtin arrays

E.g. the following should iterate through the array:

for (var i in [1, 2, 3]) {
  ...
}

Currently we get an error:

error: type 'int[3]' has no member function 'iterator'

logo

Hello İ've been drawing a logo for c star programming language. It is not final but I wonder what you guys think or wish for a logo to have.

cstar

Debug info

Show code in debugger instead of assembly

Error when compiling, do you need a C environment?

I'm trying to compile and I noticed that we can compile in a few ways. But this could be easier.
Without the visual studio tools environment installed, the compilation does not occur, generating failures and strange warnings, would this be correct? Do you need the Visual studio environment?

cxError

What environment do you need to cleanly compile the examples? which path should be inserted?

I tried to compile the example HelloWord.

cx_codeHello

Using the visual developer prompt works.

cx_visual

Parsing numbers

Looking on parsing numbers in lex.cpp, I have few notes:

  1. Octal numbers are relic of the ancient past. I think it was a PDP computer which used them regularly last time.

    Walter Bright, author of the D language recently talked, how having them in his language was mistake (here at 12:14 https://youtu.be/p22MM1wc7xQ?t=734 ). D allows leading zeros, making things more bug prone.

  2. It would improve readability, if it was possible to insert a visual separator between the numerals, e.g. underscore _:
    132_780, 0xFF60_88A1_73E9_6620, 0b0110_1110

    The separator could be single quote ', as they do it in India; or minus -, if math operators require space before and after. (This would also allow to use - in names, making them more readable.)

  3. Binary and hex numbers should always have size 1, 2, 4, or 8 bytes (filled with leading zeros, if needed). People are not used to read/write them, it is much easier to make a mistake here. Possible separator inside these numbers should preferably be also on these boundaries. If binary/hex is assigned to a known sized integer, the whole sized number should be written.

  4. A bug. This code accepts integer value out of its range (has to be negative, overflow of positives is checked):

    void main() {
        int8 a = -0xFFFF;
        println(a); 
    }
    
    
  5. Scientific float notation could be supported.

  6. Hex notation for floats could be supported, to make precise values and NaNs.

  7. In lex.ccp, function Lexer::readNumber(), I was surprised to see the name end to be used as both a local identifier and as a label. It is a bit confusing to see such a (valid by standard) trick.

llvm 8.0.0

When is delta going to be updated to use llvm version 8.0.0?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.