some :: #match {}
#match some macro (it: $T, cond: $F) -> #auto where Iterable(T) {
some :: some
+ as_iterator :: as_iterator
return some(as_iterator(it), cond);
}
every :: #match {}
#match every macro (it: $T, cond: $F) -> #auto where Iterable(T) {
every :: every
+ as_iterator :: as_iterator
return every(as_iterator(it), cond);
}
return arr;
}
+
+
+
+distributor :: #match {}
+#match distributor macro (it: $T) -> #auto where Iterable(T) {
+ distributor :: distributor;
+ as_iterator :: as_iterator;
+ return distributor(as_iterator(it));
+}
+
+#match distributor (it: Iterator($T)) -> Iterator(T) {
+ Context :: struct (T: type_expr) {
+ mutex: sync.Mutex;
+ iterator: Iterator(T);
+ }
+
+ next :: (use c: ^Context($T)) -> (T, bool) {
+ sync.scoped_mutex(^mutex);
+ return take_one(iterator);
+ }
+
+ close :: (use c: ^Context($T)) {
+ sync.mutex_destroy(^c.mutex);
+ cfree(c);
+ }
+
+ c := new(Context(T));
+ sync.mutex_init(^c.mutex);
+ c.iterator = it;
+
+ return .{c, #solidify next {T=T}, #solidify close {T=T}};
+}
+
+parallel_for :: macro (iterable: $I, thread_count: u32, body: Code) where Iterable(I) {
+ thread :: package core.thread;
+ alloc :: package core.alloc;
+
+ if thread_count != 0 {
+ dist := distributor(iterable);
+ hacky_crap_to_get_the_type_of_T(dist);
+ }
+
+ hacky_crap_to_get_the_type_of_T :: macro (dist: Iterator($T)) {
+ threads := (cast(^thread.Thread) alloc.from_stack(thread_count * sizeof thread.Thread))[0 .. (thread_count - 1)];
+ for^ threads do thread.spawn(it, ^dist, #solidify thread_function {body=body, T=T});
+
+ thread_function(body, ^dist);
+
+ for^ threads do thread.join(it);
+ dist.close(dist.data);
+ }
+
+ thread_function :: ($body: Code, iter: ^Iterator($T)) {
+ for #no_close *iter {
+ #insert body;
+ }
+ }
+}
\ No newline at end of file
// As another small shorthand, if a code block only contains one expression (like the code block above),
// then you can write by wrapping the expression in #(...).
triple_macro(#(println("Short code block!")));
+
+ // As yet another small piece of syntactic sugar, you can pass a code block to a macro or procedure in
+ // the following way:
+
+ cool_block :: macro (param: i32, body: Code) {
+ local_variable := param * 2;
+ #insert body;
+ }
+
+ cool_block(10) {
+ println(local_variable);
+ }
+
+ // Whenever there is a call at the statement level, the compiler will look one token ahead and see if there
+ // is a '{'. If there is, then the compiler will parse the '{' as the start of a code block, and will pass that
+ // code block as the last non-named argument to the function call. This allows you to write blocks of code that are
+ // prefaced by a controlling macro, which enables you do whatever crazy things you can imagine. For example, you
+ // could do something like `#pragma omp parallel for` in Onyx without relying on compiler magic.
+ //
+ // parallel_for(iterable, thread_count=4) {
+ // println(it); // Will be automatically assigned from the called macro.
+ // }
}
#load "core/std"
return;
}
-at_least_one_test_failed := false;
-
-test_cases :: (cases) => {
- for #no_close *cases {
- printf("[{}] Running test {}...\n", context.thread_id, it.source_file);
-
- proc := os.process_spawn(onyx_cmd, .["run", it.source_file]);
- defer os.process_destroy(^proc);
-
- proc_reader := io.reader_make(^proc);
- output := io.read_all(^proc_reader);
- defer memory.free_slice(^output);
-
- if exit := os.process_wait(^proc); exit != .Success {
- // Error running the test case
- print_color(.Red, "[{}] Error '{}' in test case {}.\n{}", context.thread_id, exit, it.source_file, output);
- at_least_one_test_failed = true;
- continue;
- }
-
- for expected_file: os.with_file(it.expected_file) {
- expected_reader := io.reader_make(expected_file);
- expected_output := io.read_all(^expected_reader);
-
- if output != expected_output {
- print_color(.Red, "[{}] Output did not match for {}.\n", context.thread_id, it.source_file);
- printf("Expected:\n{}\n", expected_output);
- printf("Got:\n{}\n", output);
- at_least_one_test_failed = true;
- }
- }
- }
-}
-
// The executable to use when compiling
onyx_cmd: str;
+at_least_one_test_failed := false;
settings := Settings.{};
Settings :: struct {
debug := false;
["--threads"]
- threads := 3;
+ threads := 4;
["--no-color"]
no_color := false;
find_onyx_files(settings.test_folder, ^cases);
thread_count := settings.threads;
- case_iterator := distributor(cases);
- if thread_count > 0 {
- // Stack allocate this in the future?
- threads := memory.make_slice(thread.Thread, thread_count);
+ iter.parallel_for(cases, settings.threads) {
+ // Weird macros mean I have to forward external names
+ use package core
+ onyx_cmd :: onyx_cmd
+ print_color :: print_color
+ at_least_one_test_failed :: at_least_one_test_failed
- for thread_count {
- thread.spawn(^threads[it], ^case_iterator, test_cases);
- }
+ printf("[{}] Running test {}...\n", context.thread_id, it.source_file);
- test_cases(^case_iterator);
+ proc := os.process_spawn(onyx_cmd, .["run", it.source_file]);
+ defer os.process_destroy(^proc);
- for ^ threads do thread.join(it);
+ proc_reader := io.reader_make(^proc);
+ output := io.read_all(^proc_reader);
+ defer memory.free_slice(^output);
- } else {
- test_cases(^case_iterator);
- }
+ if exit := os.process_wait(^proc); exit != .Success {
+ // Error running the test case
+ print_color(.Red, "[{}] Error '{}' in test case {}.\n{}", context.thread_id, exit, it.source_file, output);
+ at_least_one_test_failed = true;
+ continue;
+ }
+
+ for expected_file: os.with_file(it.expected_file) {
+ expected_reader := io.reader_make(expected_file);
+ expected_output := io.read_all(^expected_reader);
- case_iterator->close();
+ if output != expected_output {
+ print_color(.Red, "[{}] Output did not match for {}.\n", context.thread_id, it.source_file);
+ printf("Expected:\n{}\n", expected_output);
+ printf("Got:\n{}\n", output);
+ at_least_one_test_failed = true;
+ }
+ }
+ }
if at_least_one_test_failed {
print_color(.Red, "FAILED\n");
- // (package wasi).proc_exit(1);
+ os.exit(-1);
} else {
print_color(.Green, "SUCCESS\n");