Add channels and threads

This commit is contained in:
Bruce Hill 2024-08-11 14:47:34 -04:00
parent 3bf8ea8e12
commit 2ecb5fe885
21 changed files with 423 additions and 20 deletions

View File

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

5
ast.c
View File

@ -113,7 +113,7 @@ CORD ast_to_xml(ast_t *ast)
T(StackReference, "<StackReference>%r</StackReference>", ast_to_xml(data.value))
T(Min, "<Min>%r%r%r</Min>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key))
T(Max, "<Max>%r%r%r</Max>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key))
T(Array, "<Array>%r%r</Array>", optional_tagged_type("item-type", data.type), ast_list_to_xml(data.items))
T(Array, "<Array>%r%r</Array>", optional_tagged_type("item-type", data.item_type), ast_list_to_xml(data.items))
T(Set, "<Set>%r%r</Set>",
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, "<TableEntry>%r%r</TableEntry>", ast_to_xml(data.key), ast_to_xml(data.value))
T(Channel, "<Channel>%r</Channel>", type_ast_to_xml(data.item_type))
T(Comprehension, "<Comprehension>%r%r%r%r%r</Comprehension>", 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, "<ArrayType>%r</ArrayType>", type_ast_to_xml(data.item))
T(SetTypeAST, "<TableType>%r</TableType>", type_ast_to_xml(data.item))
T(ChannelTypeAST, "<ChannelType>%r</ChannelType>", type_ast_to_xml(data.item))
T(TableTypeAST, "<TableType>%r %r</TableType>", type_ast_to_xml(data.key), type_ast_to_xml(data.value))
T(FunctionTypeAST, "<FunctionType>%r %r</FunctionType>", 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);

10
ast.h
View File

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

102
builtins/channel.c Normal file
View File

@ -0,0 +1,102 @@
// Functions that operate on channels
#include <ctype.h>
#include <err.h>
#include <gc.h>
#include <gc/cord.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/param.h>
#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

25
builtins/channel.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
// Functions that operate on channels (thread-safe arrays)
#include <stdbool.h>
#include <gc/cord.h>
#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

View File

@ -4,6 +4,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#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

View File

@ -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, "()");

View File

@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stdint.h>
#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

57
builtins/thread.c Normal file
View File

@ -0,0 +1,57 @@
// Logic for the Thread type, representing a pthread
#include <ctype.h>
#include <err.h>
#include <gc.h>
#include <gc/cord.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/param.h>
#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

21
builtins/thread.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
// Logic for the Thread type, representing a pthread
#include <pthread.h>
#include <stdbool.h>
#include <gc/cord.h>
#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

View File

@ -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

View File

@ -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*), \

View File

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

View File

@ -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++) {

View File

@ -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

38
parse.c
View File

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

View File

@ -1,6 +1,6 @@
func main():
>> str := "Hello Amélie!"
| Testing strings like {str}
//! Testing strings like {str}
>> str:upper()
= "HELLO AMÉLIE!"

32
test/threads.tm Normal file
View File

@ -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!

View File

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

35
types.c
View File

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

View File

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