diff --git a/Makefile b/Makefile index 97801d2..6bb694b 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ G=-ggdb O=-Og CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) LDLIBS=-lgc -lcord -lm -lunistring -ldl -BUILTIN_OBJS=builtins/array.o builtins/bool.o builtins/nums.o builtins/functions.o builtins/integers.o \ - builtins/pointer.o builtins/memory.o builtins/text.o builtins/where.o builtins/c_string.o builtins/table.o \ +BUILTIN_OBJS=builtins/array.o builtins/bool.o builtins/channel.o builtins/nums.o builtins/functions.o builtins/integers.o \ + builtins/pointer.o builtins/memory.o builtins/text.o builtins/thread.o builtins/where.o builtins/c_string.o builtins/table.o \ builtins/types.o builtins/util.o builtins/files.o builtins/range.o TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm)) diff --git a/ast.c b/ast.c index 515ffa7..000c5f1 100644 --- a/ast.c +++ b/ast.c @@ -113,7 +113,7 @@ CORD ast_to_xml(ast_t *ast) T(StackReference, "%r", ast_to_xml(data.value)) T(Min, "%r%r%r", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key)) T(Max, "%r%r%r", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key)) - T(Array, "%r%r", optional_tagged_type("item-type", data.type), ast_list_to_xml(data.items)) + T(Array, "%r%r", optional_tagged_type("item-type", data.item_type), ast_list_to_xml(data.items)) T(Set, "%r%r", optional_tagged_type("item-type", data.item_type), ast_list_to_xml(data.items)) @@ -121,6 +121,7 @@ CORD ast_to_xml(ast_t *ast) optional_tagged_type("key-type", data.key_type), optional_tagged_type("value-type", data.value_type), ast_list_to_xml(data.entries), optional_tagged("fallback", data.fallback)) T(TableEntry, "%r%r", ast_to_xml(data.key), ast_to_xml(data.value)) + T(Channel, "%r", type_ast_to_xml(data.item_type)) T(Comprehension, "%r%r%r%r%r", optional_tagged("expr", data.expr), ast_list_to_xml(data.vars), optional_tagged("iter", data.iter), optional_tagged("filter", data.filter)) @@ -172,6 +173,7 @@ CORD type_ast_to_xml(type_ast_t *t) data.is_optional ? "yes" : "no", data.is_stack ? "yes" : "no", data.is_readonly ? "yes" : "no", type_ast_to_xml(data.pointed)) T(ArrayTypeAST, "%r", type_ast_to_xml(data.item)) T(SetTypeAST, "%r", type_ast_to_xml(data.item)) + T(ChannelTypeAST, "%r", type_ast_to_xml(data.item)) T(TableTypeAST, "%r %r", type_ast_to_xml(data.key), type_ast_to_xml(data.value)) T(FunctionTypeAST, "%r %r", arg_list_to_xml(data.args), type_ast_to_xml(data.ret)) #undef T @@ -224,6 +226,7 @@ bool type_ast_eq(type_ast_t *x, type_ast_t *y) } case ArrayTypeAST: return type_ast_eq(Match(x, ArrayTypeAST)->item, Match(y, ArrayTypeAST)->item); case SetTypeAST: return type_ast_eq(Match(x, SetTypeAST)->item, Match(y, SetTypeAST)->item); + case ChannelTypeAST: return type_ast_eq(Match(x, ChannelTypeAST)->item, Match(y, ChannelTypeAST)->item); case TableTypeAST: { auto tx = Match(x, TableTypeAST); auto ty = Match(y, TableTypeAST); diff --git a/ast.h b/ast.h index 41760e3..15c41d8 100644 --- a/ast.h +++ b/ast.h @@ -58,6 +58,7 @@ typedef enum { PointerTypeAST, ArrayTypeAST, SetTypeAST, + ChannelTypeAST, TableTypeAST, FunctionTypeAST, } type_ast_e; @@ -85,7 +86,7 @@ struct type_ast_s { } PointerTypeAST; struct { type_ast_t *item; - } ArrayTypeAST; + } ArrayTypeAST, ChannelTypeAST; struct { type_ast_t *key, *value; } TableTypeAST; @@ -108,7 +109,7 @@ typedef enum { BinaryOp, UpdateAssign, Length, Not, Negative, HeapAllocate, StackReference, Min, Max, - Array, Set, Table, TableEntry, Comprehension, + Array, Channel, Set, Table, TableEntry, Comprehension, FunctionDef, Lambda, FunctionCall, MethodCall, Block, @@ -178,9 +179,12 @@ struct ast_s { ast_t *lhs, *rhs, *key; } Min, Max; struct { - type_ast_t *type; + type_ast_t *item_type; ast_list_t *items; } Array; + struct { + type_ast_t *item_type; + } Channel; struct { type_ast_t *item_type; ast_list_t *items; diff --git a/builtins/channel.c b/builtins/channel.c new file mode 100644 index 0000000..0b5f741 --- /dev/null +++ b/builtins/channel.c @@ -0,0 +1,102 @@ +// Functions that operate on channels + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "array.h" +#include "functions.h" +#include "halfsiphash.h" +#include "types.h" +#include "util.h" + +public channel_t *Channel$new(void) +{ + channel_t *channel = new(channel_t); + channel->items = (array_t){}; + channel->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + channel->cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + return channel; +} + +public void Channel$push(channel_t *channel, const void *item, int64_t padded_item_size) +{ + (void)pthread_mutex_lock(&channel->mutex); + Array$insert(&channel->items, item, 0, padded_item_size); + (void)pthread_mutex_unlock(&channel->mutex); + (void)pthread_cond_signal(&channel->cond); +} + +public void Channel$push_all(channel_t *channel, array_t to_push, int64_t padded_item_size) +{ + (void)pthread_mutex_lock(&channel->mutex); + Array$insert_all(&channel->items, to_push, 0, padded_item_size); + (void)pthread_mutex_unlock(&channel->mutex); + (void)pthread_cond_signal(&channel->cond); +} + +public void Channel$pop(channel_t *channel, void *out, int64_t item_size, int64_t padded_item_size) +{ + (void)pthread_mutex_lock(&channel->mutex); + while (channel->items.length == 0) + pthread_cond_wait(&channel->cond, &channel->mutex); + memcpy(out, channel->items.data, item_size); + Array$remove(&channel->items, 1, 1, padded_item_size); + (void)pthread_mutex_unlock(&channel->mutex); +} + +public array_t Channel$view(channel_t *channel) +{ + (void)pthread_mutex_lock(&channel->mutex); + ARRAY_INCREF(channel->items); + array_t ret = channel->items; + (void)pthread_mutex_unlock(&channel->mutex); + return ret; +} + +public void Channel$clear(channel_t *channel) +{ + (void)pthread_mutex_lock(&channel->mutex); + Array$clear(&channel->items); + (void)pthread_mutex_unlock(&channel->mutex); +} + +public uint32_t Channel$hash(const channel_t **channel, const TypeInfo *type) +{ + (void)type; + uint32_t hash; + halfsiphash(*channel, sizeof(channel_t*), TOMO_HASH_KEY, (uint8_t*)&hash, sizeof(hash)); + return hash; +} + +public int32_t Channel$compare(const channel_t **x, const channel_t **y, const TypeInfo *type) +{ + (void)type; + return (*x > *y) - (*x < *y); +} + +bool Channel$equal(const channel_t **x, const channel_t **y, const TypeInfo *type) +{ + (void)type; + return (*x == *y); +} + +CORD Channel$as_text(const channel_t **channel, bool colorize, const TypeInfo *type) +{ + const TypeInfo *item_type = type->ChannelInfo.item; + if (!channel) { + CORD typename = generic_as_text(NULL, false, item_type); + return colorize ? CORD_asprintf("\x1b[34;1m|:%s|\x1b[m", typename) : CORD_all("|:", typename, "|"); + } + CORD typename = generic_as_text(NULL, false, item_type); + return CORD_asprintf(colorize ? "\x1b[34;1m|:%s|<%p>\x1b[m" : "|:%s|<%p>", typename, *channel); +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/channel.h b/builtins/channel.h new file mode 100644 index 0000000..2ce7183 --- /dev/null +++ b/builtins/channel.h @@ -0,0 +1,25 @@ +#pragma once + +// Functions that operate on channels (thread-safe arrays) + +#include +#include + +#include "datatypes.h" +#include "types.h" +#include "util.h" + +channel_t *Channel$new(void); +void Channel$push(channel_t *channel, const void *item, int64_t padded_item_size); +#define Channel$push_value(channel, item, padded_item_size) ({ __typeof(item) _item = item; Channel$push(channel, &_item, padded_item_size); }) +void Channel$push_all(channel_t *channel, array_t to_push, int64_t padded_item_size); +void Channel$pop(channel_t *channel, void *out, int64_t item_size, int64_t padded_item_size); +#define Channel$pop_value(channel, t, padded_item_size) ({ t _val; Channel$pop(channel, &_val, sizeof(t), padded_item_size); _val; }) +void Channel$clear(channel_t *channel); +array_t Channel$view(channel_t *channel); +uint32_t Channel$hash(const channel_t **channel, const TypeInfo *type); +int32_t Channel$compare(const channel_t **x, const channel_t **y, const TypeInfo *type); +bool Channel$equal(const channel_t **x, const channel_t **y, const TypeInfo *type); +CORD Channel$as_text(const channel_t **channel, bool colorize, const TypeInfo *type); + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/datatypes.h b/builtins/datatypes.h index b0f62d0..41a67a5 100644 --- a/builtins/datatypes.h +++ b/builtins/datatypes.h @@ -4,6 +4,7 @@ #include #include +#include #define ARRAY_LENGTH_BITS 42 #define ARRAY_FREE_BITS 6 @@ -57,4 +58,10 @@ typedef struct Range_s { int64_t first, last, step; } Range_t; +typedef struct { + array_t items; + pthread_mutex_t mutex; + pthread_cond_t cond; +} channel_t; + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/functions.c b/builtins/functions.c index 4735081..4e7d25a 100644 --- a/builtins/functions.c +++ b/builtins/functions.c @@ -13,6 +13,7 @@ #include "array.h" #include "bool.h" +#include "channel.h" #include "files.h" #include "functions.h" #include "halfsiphash.h" @@ -101,6 +102,7 @@ public uint32_t generic_hash(const void *obj, const TypeInfo *type) case PointerInfo: case FunctionInfo: return Pointer$hash(obj, type); case TextInfo: return Text$hash(obj); case ArrayInfo: return Array$hash(obj, type); + case ChannelInfo: return Channel$hash((const channel_t**)obj, type); case TableInfo: return Table$hash(obj, type); case EmptyStruct: return 0; case CustomInfo: @@ -122,6 +124,7 @@ public int32_t generic_compare(const void *x, const void *y, const TypeInfo *typ case PointerInfo: case FunctionInfo: return Pointer$compare(x, y, type); case TextInfo: return Text$compare(x, y); case ArrayInfo: return Array$compare(x, y, type); + case ChannelInfo: return Channel$compare((const channel_t**)x, (const channel_t**)y, type); case TableInfo: return Table$compare(x, y, type); case EmptyStruct: return 0; case CustomInfo: @@ -140,6 +143,7 @@ public bool generic_equal(const void *x, const void *y, const TypeInfo *type) case PointerInfo: case FunctionInfo: return Pointer$equal(x, y, type); case TextInfo: return Text$equal(x, y); case ArrayInfo: return Array$equal(x, y, type); + case ChannelInfo: return Channel$equal((const channel_t**)x, (const channel_t**)y, type); case TableInfo: return Table$equal(x, y, type); case EmptyStruct: return true; case CustomInfo: @@ -159,6 +163,7 @@ public CORD generic_as_text(const void *obj, bool colorize, const TypeInfo *type case FunctionInfo: return Func$as_text(obj, colorize, type); case TextInfo: return Text$as_text(obj, colorize, type); case ArrayInfo: return Array$as_text(obj, colorize, type); + case ChannelInfo: return Channel$as_text((const channel_t**)obj, colorize, type); case TableInfo: return Table$as_text(obj, colorize, type); case TypeInfoInfo: return Type$as_text(obj, colorize, type); case EmptyStruct: return colorize ? CORD_all("\x1b[0;1m", type->EmptyStruct.name, "\x1b[m()") : CORD_all(type->EmptyStruct.name, "()"); diff --git a/builtins/functions.h b/builtins/functions.h index a62865b..ac4fbf8 100644 --- a/builtins/functions.h +++ b/builtins/functions.h @@ -6,6 +6,7 @@ #include #include +#include "datatypes.h" #include "types.h" extern uint8_t TOMO_HASH_KEY[8]; @@ -26,6 +27,7 @@ uint32_t generic_hash(const void *obj, const TypeInfo *type); int32_t generic_compare(const void *x, const void *y, const TypeInfo *type); bool generic_equal(const void *x, const void *y, const TypeInfo *type); CORD generic_as_text(const void *obj, bool colorize, const TypeInfo *type); +closure_t spawn(closure_t fn); bool pop_flag(char **argv, int *i, const char *flag, CORD *result); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/thread.c b/builtins/thread.c new file mode 100644 index 0000000..b958691 --- /dev/null +++ b/builtins/thread.c @@ -0,0 +1,57 @@ +// Logic for the Thread type, representing a pthread + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "array.h" +#include "functions.h" +#include "halfsiphash.h" +#include "types.h" +#include "util.h" + +public pthread_t *Thread$new(closure_t fn) +{ + pthread_t *thread = new(pthread_t); + pthread_create(thread, NULL, fn.fn, fn.userdata); + return thread; +} + +public void Thread$join(pthread_t *thread) +{ + pthread_join(*thread, NULL); +} + +public void Thread$cancel(pthread_t *thread) +{ + pthread_cancel(*thread); +} + +public void Thread$detach(pthread_t *thread) +{ + pthread_detach(*thread); +} + +CORD Thread$as_text(const pthread_t **thread, bool colorize, const TypeInfo *type) +{ + (void)type; + if (!thread) { + return colorize ? "\x1b[34;1mThread\x1b[m" : "Thread"; + } + return CORD_asprintf(colorize ? "\x1b[34;1mThread(%p)\x1b[m" : "Thread(%p)", *thread); +} + +public const TypeInfo Thread = { + .size=sizeof(pthread_t*), .align=__alignof(pthread_t*), + .tag=CustomInfo, + .CustomInfo={.as_text=(void*)Thread$as_text}, +}; + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/thread.h b/builtins/thread.h new file mode 100644 index 0000000..efccae3 --- /dev/null +++ b/builtins/thread.h @@ -0,0 +1,21 @@ +#pragma once + +// Logic for the Thread type, representing a pthread + +#include +#include +#include + +#include "datatypes.h" +#include "types.h" +#include "util.h" + +pthread_t *Thread$new(closure_t fn); +void Thread$cancel(pthread_t *thread); +void Thread$join(pthread_t *thread); +void Thread$detach(pthread_t *thread); +CORD Thread$as_text(const pthread_t **thread, bool colorize, const TypeInfo *type); + +extern TypeInfo Thread; + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/tomo.h b/builtins/tomo.h index 595d631..bbf48e2 100644 --- a/builtins/tomo.h +++ b/builtins/tomo.h @@ -12,6 +12,7 @@ #include "array.h" #include "bool.h" #include "c_string.h" +#include "channel.h" #include "datatypes.h" #include "functions.h" #include "halfsiphash.h" @@ -23,6 +24,7 @@ #include "range.h" #include "table.h" #include "text.h" +#include "thread.h" #include "types.h" // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/types.h b/builtins/types.h index 4184e8f..70f8dc0 100644 --- a/builtins/types.h +++ b/builtins/types.h @@ -18,7 +18,7 @@ typedef CORD (*str_fn_t)(const void*, bool, const struct TypeInfo*); typedef struct TypeInfo { int64_t size, align; struct { // Anonymous tagged union for convenience - enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, TableInfo, FunctionInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag; + enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag; union { struct { equal_fn_t equal; @@ -36,7 +36,7 @@ typedef struct TypeInfo { } TextInfo; struct { const struct TypeInfo *item; - } ArrayInfo; + } ArrayInfo, ChannelInfo; struct { const struct TypeInfo *key, *value; } TableInfo; @@ -60,6 +60,8 @@ typedef struct TypeInfo { .tag=ArrayInfo, .ArrayInfo.item=item_info}) #define $SetInfo(item_info) &((TypeInfo){.size=sizeof(table_t), .align=__alignof__(table_t), \ .tag=TableInfo, .TableInfo.key=item_info, .TableInfo.value=&$Void}) +#define $ChannelInfo(item_info) &((TypeInfo){.size=sizeof(channel_t), .align=__alignof__(channel_t), \ + .tag=ChannelInfo, .ChannelInfo.item=item_info}) #define $TableInfo(key_expr, value_expr) &((TypeInfo){.size=sizeof(table_t), .align=__alignof__(table_t), \ .tag=TableInfo, .TableInfo.key=key_expr, .TableInfo.value=value_expr}) #define $FunctionInfo(typestr) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \ diff --git a/compile.c b/compile.c index d2081b4..8e8f7c6 100644 --- a/compile.c +++ b/compile.c @@ -134,6 +134,7 @@ CORD compile_type(type_t *t) } case ArrayType: return "array_t"; case SetType: return "table_t"; + case ChannelType: return "channel_t*"; case TableType: return "table_t"; case FunctionType: { auto fn = Match(t, FunctionType); @@ -148,6 +149,8 @@ CORD compile_type(type_t *t) case PointerType: return CORD_cat(compile_type(Match(t, PointerType)->pointed), "*"); case StructType: { auto s = Match(t, StructType); + if (t == THREAD_TYPE) + return "pthread_t*"; return CORD_all("struct ", namespace_prefix(s->env->libname, s->env->namespace->parent), s->name, "_s"); } case EnumType: { @@ -1103,6 +1106,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color) } case ArrayType: return CORD_asprintf("Array$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case SetType: return CORD_asprintf("Table$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); + case ChannelType: return CORD_asprintf("Channel$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case TableType: return CORD_asprintf("Table$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); @@ -1742,6 +1746,12 @@ CORD compile(env_t *env, ast_t *ast) return code; } } + case Channel: { + type_t *item_t = parse_type_ast(env, Match(ast, Channel)->item_type); + if (!can_send_over_channel(item_t)) + code_err(ast, "This item type can't be sent over a channel because it contains reference to memory that may not be thread-safe."); + return "Channel$new()"; + } case Table: { auto table = Match(ast, Table); if (!table->entries) { @@ -1952,7 +1962,7 @@ CORD compile(env_t *env, ast_t *ast) case ArrayType: { // TODO: check for readonly type_t *item_t = Match(self_value_t, ArrayType)->item_type; - CORD padded_item_size = CORD_asprintf("%ld", padded_type_size(Match(self_value_t, ArrayType)->item_type)); + CORD padded_item_size = CORD_asprintf("%ld", padded_type_size(item_t)); if (streq(call->name, "insert")) { CORD self = compile_to_pointer_depth(env, call->self, 1, false); arg_t *arg_spec = new(arg_t, .name="item", .type=item_t, @@ -1979,7 +1989,7 @@ CORD compile(env_t *env, ast_t *ast) CORD self = compile_to_pointer_depth(env, call->self, 0, false); arg_t *arg_spec = new(arg_t, .name="count", .type=Type(IntType, .bits=64), .next=new(arg_t, .name="weights", .type=Type(ArrayType, .item_type=Type(NumType, .bits=64)), - .default_val=FakeAST(Array, .type=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Num")))); + .default_val=FakeAST(Array, .item_type=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Num")))); return CORD_all("Array$sample(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ", padded_item_size, ")"); } else if (streq(call->name, "shuffle")) { @@ -2124,6 +2134,33 @@ CORD compile(env_t *env, ast_t *ast) ", ", compile_type_info(env, self_value_t), ")"); } else code_err(ast, "There is no '%s' method for tables", call->name); } + case ChannelType: { + type_t *item_t = Match(self_value_t, ChannelType)->item_type; + CORD padded_item_size = CORD_asprintf("%ld", padded_type_size(item_t)); + if (streq(call->name, "push")) { + CORD self = compile_to_pointer_depth(env, call->self, 0, false); + arg_t *arg_spec = new(arg_t, .name="item", .type=item_t); + return CORD_all("Channel$push_value(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ", + padded_item_size, ")"); + } else if (streq(call->name, "push_all")) { + CORD self = compile_to_pointer_depth(env, call->self, 0, false); + arg_t *arg_spec = new(arg_t, .name="to_push", .type=Type(ArrayType, .item_type=item_t)); + return CORD_all("Channel$push_all(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ", + padded_item_size, ")"); + } else if (streq(call->name, "pop")) { + CORD self = compile_to_pointer_depth(env, call->self, 0, false); + (void)compile_arguments(env, ast, NULL, call->args); + return CORD_all("Channel$pop_value(", self, ", ", compile_type(item_t), ", ", padded_item_size, ")"); + } else if (streq(call->name, "clear")) { + CORD self = compile_to_pointer_depth(env, call->self, 0, false); + (void)compile_arguments(env, ast, NULL, call->args); + return CORD_all("Channel$clear(", self, ")"); + } else if (streq(call->name, "view")) { + CORD self = compile_to_pointer_depth(env, call->self, 0, false); + (void)compile_arguments(env, ast, NULL, call->args); + return CORD_all("Channel$view(", self, ")"); + } else code_err(ast, "There is no '%s' method for channels", call->name); + } case TableType: { auto table = Match(self_value_t, TableType); if (streq(call->name, "get")) { @@ -2499,6 +2536,10 @@ CORD compile_type_info(env_t *env, type_t *t) type_t *item_type = Match(t, SetType)->item_type; return CORD_all("$SetInfo(", compile_type_info(env, item_type), ")"); } + case ChannelType: { + type_t *item_t = Match(t, ChannelType)->item_type; + return CORD_asprintf("$ChannelInfo(%r)", compile_type_info(env, item_t)); + } case TableType: { type_t *key_type = Match(t, TableType)->key_type; type_t *value_type = Match(t, TableType)->value_type; diff --git a/environment.c b/environment.c index 84f6880..8de3fc2 100644 --- a/environment.c +++ b/environment.c @@ -11,6 +11,7 @@ type_t *TEXT_TYPE = NULL; type_t *RANGE_TYPE = NULL; +public type_t *THREAD_TYPE = NULL; env_t *new_compilation_unit(CORD *libname) { @@ -68,6 +69,11 @@ env_t *new_compilation_unit(CORD *libname) .next=new(arg_t, .name="step", .type=INT_TYPE, .default_val=FakeAST(Int, .i=1, .bits=64))))); } + { + env_t *thread_env = namespace_env(env, "Thread"); + THREAD_TYPE = Type(StructType, .name="Thread", .env=thread_env, .opaque=true); + } + struct { const char *name; type_t *type; @@ -215,6 +221,12 @@ env_t *new_compilation_unit(CORD *libname) {"upper", "Text$upper", "func(text:Text)->Text"}, {"without", "Text$without", "func(text:Text, target:Text, where=Where.Anywhere)->Text"}, )}, + {"Thread", THREAD_TYPE, "pthread_t*", "Thread", TypedArray(ns_entry_t, + {"new", "Thread$new", "func(fn:func()->Void)->Thread"}, + {"cancel", "Thread$cancel", "func(thread:Thread)->Void"}, + {"join", "Thread$join", "func(thread:Thread)->Void"}, + {"detach", "Thread$detach", "func(thread:Thread)->Void"}, + )}, }; for (size_t i = 0; i < sizeof(global_types)/sizeof(global_types[0]); i++) { diff --git a/environment.h b/environment.h index b72d481..3957bd1 100644 --- a/environment.h +++ b/environment.h @@ -75,5 +75,6 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); #define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__) extern type_t *TEXT_TYPE; extern type_t *RANGE_TYPE; +extern type_t *THREAD_TYPE; // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/parse.c b/parse.c index 60b68ed..ac24297 100644 --- a/parse.c +++ b/parse.c @@ -82,6 +82,8 @@ static ast_t *parse_index_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_optional_suffix(parse_ctx_t *ctx, ast_t *lhs); static arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos, bool allow_unnamed); +static PARSER(parse_array); +static PARSER(parse_channel); static PARSER(parse_for); static PARSER(parse_do); static PARSER(parse_while); @@ -371,10 +373,12 @@ const char *get_id(const char **inout) { } bool comment(const char **pos) { - if (!match(pos, "//")) + if ((*pos)[0] == '/' && (*pos)[1] == '/' && (*pos)[2] != '!') { + *pos += 2 + strcspn(*pos, "\r\n"); + return true; + } else { return false; - some_not(pos, "\r\n"); - return true; + } } bool indent(parse_ctx_t *ctx, const char **out) { @@ -547,6 +551,15 @@ type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos) { return NewTypeAST(ctx->file, start, pos, ArrayTypeAST, .item=type); } +type_ast_t *parse_channel_type(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match(&pos, "|")) return NULL; + type_ast_t *type = expect(ctx, start, &pos, parse_type, + "I couldn't parse a channel item type after this point"); + expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this channel"); + return NewTypeAST(ctx->file, start, pos, ChannelTypeAST, .item=type); +} + type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) { const char *start = pos; bool is_stack; @@ -588,6 +601,7 @@ type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) { bool success = (false || (type=parse_pointer_type(ctx, pos)) || (type=parse_array_type(ctx, pos)) + || (type=parse_channel_type(ctx, pos)) || (type=parse_table_type(ctx, pos)) || (type=parse_set_type(ctx, pos)) || (type=parse_type_name(ctx, pos)) @@ -662,6 +676,14 @@ static inline bool match_separator(const char **pos) { // Either comma or newlin } } +PARSER(parse_channel) { + const char *start = pos; + if (!match(&pos, "|:")) return NULL; + type_ast_t *item_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a type for this channel"); + expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this channel"); + return NewAST(ctx->file, start, pos, Channel, .item_type=item_type); +} + PARSER(parse_array) { const char *start = pos; if (!match(&pos, "[")) return NULL; @@ -696,7 +718,7 @@ PARSER(parse_array) { parser_err(ctx, start, pos, "Empty arrays must specify what type they would contain (e.g. [:Int])"); REVERSE_LIST(items); - return NewAST(ctx->file, start, pos, Array, .type=item_type, .items=items); + return NewAST(ctx->file, start, pos, Array, .item_type=item_type, .items=items); } PARSER(parse_table) { @@ -765,7 +787,7 @@ PARSER(parse_table) { whitespace(&pos); expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table"); - return NewAST(ctx->file, start, pos, Table, .key_type=key_type, .value_type=value_type, .entries=entries, .fallback=fallback); + return NewAST(ctx->file, start, pos, Table, .key_type=key_type, .value_type=value_type, .entries=entries); } PARSER(parse_set) { @@ -1002,6 +1024,9 @@ PARSER(parse_when) { } } + tmp = pos; + if (!match(&tmp, ":")) + parser_err(ctx, tmp, tmp, "I expected a colon ':' after this clause"); ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'when' clause"); clauses = new(when_clause_t, .tag_name=tag_name, .args=args, .body=body, .next=clauses); tmp = pos; @@ -1342,6 +1367,7 @@ PARSER(parse_term_no_suffix) { || (term=parse_set(ctx, pos)) || (term=parse_var(ctx, pos)) || (term=parse_array(ctx, pos)) + || (term=parse_channel(ctx, pos)) || (term=parse_reduction(ctx, pos)) || (term=parse_pass(ctx, pos)) || (term=parse_defer(ctx, pos)) @@ -2109,7 +2135,7 @@ PARSER(parse_doctest) { PARSER(parse_say) { const char *start = pos; - if (!match(&pos, "|")) return NULL; + if (!match(&pos, "//!")) return NULL; spaces(&pos); ast_list_t *chunks = NULL; diff --git a/test/text.tm b/test/text.tm index 27c8c08..4051a16 100644 --- a/test/text.tm +++ b/test/text.tm @@ -1,6 +1,6 @@ func main(): >> str := "Hello Amélie!" - | Testing strings like {str} + //! Testing strings like {str} >> str:upper() = "HELLO AMÉLIE!" diff --git a/test/threads.tm b/test/threads.tm new file mode 100644 index 0000000..3368510 --- /dev/null +++ b/test/threads.tm @@ -0,0 +1,32 @@ +enum Job(Increment(x:Int), Decrement(x:Int)) + +func main(): + jobs := |:Job| + results := |:Int| + >> thread := Thread.new(func(): + //! In another thread! + while yes: + when jobs:pop() is Increment(x): + >> results:push(x+1) + is Decrement(x): + >> results:push(x-1) + ) + + >> jobs:push(Increment(5)) + >> jobs:push(Decrement(100)) + + >> results:pop() + = 6 + + >> jobs:push(Increment(1000)) + >> results:pop() + = 99 + + >> results:pop() + = 1001 + + //! Canceling... + >> thread:cancel() + //! Joining... + >> thread:join() + //! Done! diff --git a/typecheck.c b/typecheck.c index 2d1ba40..88d862b 100644 --- a/typecheck.c +++ b/typecheck.c @@ -65,6 +65,17 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) padded_type_size(item_t), ARRAY_MAX_STRIDE); return Type(SetType, .item_type=item_t); } + case ChannelTypeAST: { + type_ast_t *item_type = Match(ast, ChannelTypeAST)->item; + type_t *item_t = parse_type_ast(env, item_type); + if (!item_t) code_err(item_type, "I can't figure out what this type is."); + if (!can_send_over_channel(item_t)) + code_err(ast, "This item type can't be sent over a channel because it contains reference to memory that may not be thread-safe."); + if (padded_type_size(item_t) > ARRAY_MAX_STRIDE) + code_err(ast, "This channel holds items that take up %ld bytes, but the maximum supported size is %ld bytes.", + padded_type_size(item_t), ARRAY_MAX_STRIDE); + return Type(ChannelType, .item_type=item_t); + } case TableTypeAST: { type_ast_t *key_type_ast = Match(ast, TableTypeAST)->key; type_t *key_type = parse_type_ast(env, key_type_ast); @@ -515,8 +526,8 @@ type_t *get_type(env_t *env, ast_t *ast) case Array: { auto array = Match(ast, Array); type_t *item_type = NULL; - if (array->type) { - item_type = parse_type_ast(env, array->type); + if (array->item_type) { + item_type = parse_type_ast(env, array->item_type); } else if (array->items) { for (ast_list_t *item = array->items; item; item = item->next) { ast_t *item_ast = item->ast; @@ -571,6 +582,11 @@ type_t *get_type(env_t *env, ast_t *ast) code_err(ast, "Sets cannot hold stack references because the set may outlive the reference's stack frame."); return Type(SetType, .item_type=item_type); } + case Channel: { + auto channel = Match(ast, Channel); + type_t *item_type = parse_type_ast(env, channel->item_type); + return Type(ChannelType, .item_type=item_type); + } case Table: { auto table = Match(ast, Table); type_t *key_type = NULL, *value_type = NULL; @@ -727,6 +743,14 @@ type_t *get_type(env_t *env, ast_t *ast) else if (streq(call->name, "is_superset_of")) return Type(BoolType); else code_err(ast, "There is no '%s' method for sets", call->name); } + case ChannelType: { + if (streq(call->name, "push")) return Type(VoidType); + else if (streq(call->name, "push_all")) return Type(VoidType); + else if (streq(call->name, "pop")) return Match(self_value_t, ChannelType)->item_type; + else if (streq(call->name, "clear")) return Type(VoidType); + else if (streq(call->name, "view")) return Type(ArrayType, .item_type=Match(self_value_t, ChannelType)->item_type); + else code_err(ast, "There is no '%s' method for arrays", call->name); + } case TableType: { auto table = Match(self_value_t, TableType); if (streq(call->name, "get")) return table->value_type; diff --git a/types.c b/types.c index 5025ee9..c89e0db 100644 --- a/types.c +++ b/types.c @@ -25,6 +25,10 @@ CORD type_to_cord(type_t *t) { auto array = Match(t, ArrayType); return CORD_asprintf("[%r]", type_to_cord(array->item_type)); } + case ChannelType: { + auto array = Match(t, ChannelType); + return CORD_asprintf("||%r", type_to_cord(array->item_type)); + } case TableType: { auto table = Match(t, TableType); return CORD_asprintf("{%r:%r}", type_to_cord(table->key_type), type_to_cord(table->value_type)); @@ -205,6 +209,7 @@ bool has_heap_memory(type_t *t) { switch (t->tag) { case ArrayType: return true; + case ChannelType: return true; case TableType: return true; case SetType: return true; case PointerType: return true; @@ -226,6 +231,31 @@ bool has_heap_memory(type_t *t) } } +bool can_send_over_channel(type_t *t) +{ + switch (t->tag) { + case ArrayType: return true; + case ChannelType: return true; + case TableType: return true; + case PointerType: return false; + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (!can_send_over_channel(field->type)) + return false; + } + return true; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && !can_send_over_channel(tag->type)) + return false; + } + return true; + } + default: return true; + } +} + bool has_stack_memory(type_t *t) { switch (t->tag) { @@ -313,6 +343,7 @@ bool can_leave_uninitialized(type_t *t) case PointerType: return Match(t, PointerType)->is_optional; case ArrayType: case IntType: case NumType: case BoolType: return true; + case ChannelType: return false; case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { if (!can_leave_uninitialized(field->type)) @@ -335,6 +366,7 @@ static bool _can_have_cycles(type_t *t, table_t *seen) { switch (t->tag) { case ArrayType: return _can_have_cycles(Match(t, ArrayType)->item_type, seen); + case ChannelType: return _can_have_cycles(Match(t, ChannelType)->item_type, seen); case TableType: { auto table = Match(t, TableType); return _can_have_cycles(table->key_type, seen) || _can_have_cycles(table->value_type, seen); @@ -375,6 +407,7 @@ type_t *replace_type(type_t *t, type_t *target, type_t *replacement) switch (t->tag) { case ArrayType: return REPLACED_MEMBER(t, ArrayType, item_type); case SetType: return REPLACED_MEMBER(t, SetType, item_type); + case ChannelType: return REPLACED_MEMBER(t, ChannelType, item_type); case TableType: { t = REPLACED_MEMBER(t, TableType, key_type); t = REPLACED_MEMBER(t, TableType, value_type); @@ -420,6 +453,7 @@ size_t type_size(type_t *t) case TextType: return sizeof(CORD); case ArrayType: return sizeof(array_t); case SetType: return sizeof(table_t); + case ChannelType: return sizeof(channel_t*); case TableType: return sizeof(table_t); case FunctionType: return sizeof(void*); case ClosureType: return sizeof(struct {void *fn, *userdata;}); @@ -473,6 +507,7 @@ size_t type_align(type_t *t) case TextType: return __alignof__(CORD); case SetType: return __alignof__(table_t); case ArrayType: return __alignof__(array_t); + case ChannelType: return __alignof__(channel_t*); case TableType: return __alignof__(table_t); case FunctionType: return __alignof__(void*); case ClosureType: return __alignof__(struct {void *fn, *userdata;}); diff --git a/types.h b/types.h index 8e4b797..f25e7c8 100644 --- a/types.h +++ b/types.h @@ -48,6 +48,7 @@ struct type_s { CStringType, TextType, ArrayType, + ChannelType, SetType, TableType, FunctionType, @@ -78,7 +79,7 @@ struct type_s { } TextType; struct { type_t *item_type; - } ArrayType; + } ArrayType, ChannelType; struct { type_t *item_type; } SetType; @@ -134,6 +135,7 @@ typedef enum {NUM_PRECISION_EQUAL, NUM_PRECISION_LESS, NUM_PRECISION_MORE, NUM_P precision_cmp_e compare_precision(type_t *a, type_t *b); bool has_heap_memory(type_t *t); bool has_stack_memory(type_t *t); +bool can_send_over_channel(type_t *t); bool can_promote(type_t *actual, type_t *needed); bool can_leave_uninitialized(type_t *t); bool can_have_cycles(type_t *t);