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);