Replace threads with generic mutexed datastructures.

This commit is contained in:
Bruce Hill 2025-01-02 16:24:07 -05:00
parent 2fcf1939bb
commit be384c0caa
26 changed files with 126 additions and 639 deletions

View File

@ -30,7 +30,7 @@ O=-Og
CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO)
CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')" CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')"
LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdlib/channels.o stdlib/nums.o stdlib/integers.o \ BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdlib/nums.o stdlib/integers.o \
stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \ stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \
stdlib/types.o stdlib/util.o stdlib/files.o stdlib/ranges.o stdlib/shell.o stdlib/paths.o stdlib/rng.o \ stdlib/types.o stdlib/util.o stdlib/files.o stdlib/ranges.o stdlib/shell.o stdlib/paths.o stdlib/rng.o \
stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o \ stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o \

View File

@ -77,8 +77,7 @@ of many language features or the other example programs/modules in
- Built-in doctests with syntax highlighting - Built-in doctests with syntax highlighting
- [Automatic command line argument parsing with type safety](docs/command-line-parsing.md) - [Automatic command line argument parsing with type safety](docs/command-line-parsing.md)
- [Easy interoperability with C](docs/c-interoperability.md) - [Easy interoperability with C](docs/c-interoperability.md)
- Support for [POSIX threads](docs/threads.tm) and thread-safe message passing - Support for [POSIX threads](docs/threads.tm) and mutex-guarded datastructures.
queues over [channels](docs/channels.tm).
- Built-in [data serialization and deserialization](docs/serialization.md). - Built-in [data serialization and deserialization](docs/serialization.md).
## Dependencies ## Dependencies

3
ast.c
View File

@ -116,6 +116,7 @@ CORD ast_to_xml(ast_t *ast)
T(Not, "<Not>%r</Not>", ast_to_xml(data.value)) T(Not, "<Not>%r</Not>", ast_to_xml(data.value))
T(HeapAllocate, "<HeapAllocate>%r</HeapAllocate>", ast_to_xml(data.value)) T(HeapAllocate, "<HeapAllocate>%r</HeapAllocate>", ast_to_xml(data.value))
T(StackReference, "<StackReference>%r</StackReference>", ast_to_xml(data.value)) T(StackReference, "<StackReference>%r</StackReference>", ast_to_xml(data.value))
T(Mutexed, "<Mutexed>%r</Mutexed>", 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(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(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.item_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))
@ -127,7 +128,6 @@ CORD ast_to_xml(ast_t *ast)
optional_tagged("default-value", data.default_value), optional_tagged("default-value", data.default_value),
ast_list_to_xml(data.entries), optional_tagged("fallback", data.fallback)) 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(TableEntry, "<TableEntry>%r%r</TableEntry>", ast_to_xml(data.key), ast_to_xml(data.value))
T(Channel, "<Channel>%r%r</Channel>", type_ast_to_xml(data.item_type), optional_tagged("max-size", data.max_size))
T(Comprehension, "<Comprehension>%r%r%r%r%r</Comprehension>", optional_tagged("expr", data.expr), 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), ast_list_to_xml(data.vars), optional_tagged("iter", data.iter),
optional_tagged("filter", data.filter)) optional_tagged("filter", data.filter))
@ -182,7 +182,6 @@ CORD type_ast_to_xml(type_ast_t *t)
data.is_stack ? "yes" : "no", type_ast_to_xml(data.pointed)) data.is_stack ? "yes" : "no", type_ast_to_xml(data.pointed))
T(ArrayTypeAST, "<ArrayType>%r</ArrayType>", type_ast_to_xml(data.item)) T(ArrayTypeAST, "<ArrayType>%r</ArrayType>", type_ast_to_xml(data.item))
T(SetTypeAST, "<TableType>%r</TableType>", 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(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)) T(FunctionTypeAST, "<FunctionType>%r %r</FunctionType>", arg_list_to_xml(data.args), type_ast_to_xml(data.ret))
T(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type) T(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type)

13
ast.h
View File

@ -75,7 +75,6 @@ typedef enum {
PointerTypeAST, PointerTypeAST,
ArrayTypeAST, ArrayTypeAST,
SetTypeAST, SetTypeAST,
ChannelTypeAST,
TableTypeAST, TableTypeAST,
FunctionTypeAST, FunctionTypeAST,
OptionalTypeAST, OptionalTypeAST,
@ -103,7 +102,7 @@ struct type_ast_s {
} PointerTypeAST; } PointerTypeAST;
struct { struct {
type_ast_t *item; type_ast_t *item;
} ArrayTypeAST, ChannelTypeAST; } ArrayTypeAST;
struct { struct {
type_ast_t *key, *value; type_ast_t *key, *value;
ast_t *default_value; ast_t *default_value;
@ -128,9 +127,9 @@ typedef enum {
TextLiteral, TextJoin, PrintStatement, TextLiteral, TextJoin, PrintStatement,
Declare, Assign, Declare, Assign,
BinaryOp, UpdateAssign, BinaryOp, UpdateAssign,
Not, Negative, HeapAllocate, StackReference, Not, Negative, HeapAllocate, StackReference, Mutexed,
Min, Max, Min, Max,
Array, Channel, Set, Table, TableEntry, Comprehension, Array, Set, Table, TableEntry, Comprehension,
FunctionDef, Lambda, FunctionDef, Lambda,
FunctionCall, MethodCall, FunctionCall, MethodCall,
Block, Block,
@ -194,7 +193,7 @@ struct ast_s {
} BinaryOp, UpdateAssign; } BinaryOp, UpdateAssign;
struct { struct {
ast_t *value; ast_t *value;
} Not, Negative, HeapAllocate, StackReference; } Not, Negative, HeapAllocate, StackReference, Mutexed;
struct { struct {
ast_t *lhs, *rhs, *key; ast_t *lhs, *rhs, *key;
} Min, Max; } Min, Max;
@ -202,10 +201,6 @@ struct ast_s {
type_ast_t *item_type; type_ast_t *item_type;
ast_list_t *items; ast_list_t *items;
} Array; } Array;
struct {
type_ast_t *item_type;
ast_t *max_size;
} Channel;
struct { struct {
type_ast_t *item_type; type_ast_t *item_type;
ast_list_t *items; ast_list_t *items;

View File

@ -64,6 +64,11 @@ static bool promote(env_t *env, ast_t *ast, CORD *code, type_t *actual, type_t *
if (!can_promote(actual, needed)) if (!can_promote(actual, needed))
return false; return false;
if (needed->tag == ClosureType && actual->tag == FunctionType) {
*code = CORD_all("((Closure_t){", *code, ", NULL})");
return true;
}
// Optional promotion: // Optional promotion:
if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) { if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) {
*code = promote_to_optional(actual, *code); *code = promote_to_optional(actual, *code);
@ -136,11 +141,6 @@ static bool promote(env_t *env, ast_t *ast, CORD *code, type_t *actual, type_t *
if (needed->tag == TableType && actual->tag == TableType) if (needed->tag == TableType && actual->tag == TableType)
return true; return true;
if (needed->tag == ClosureType && actual->tag == FunctionType) {
*code = CORD_all("((Closure_t){", *code, ", NULL})");
return true;
}
if (needed->tag == ClosureType && actual->tag == ClosureType) if (needed->tag == ClosureType && actual->tag == ClosureType)
return true; return true;
@ -271,7 +271,6 @@ CORD compile_type(type_t *t)
} }
case ArrayType: return "Array_t"; case ArrayType: return "Array_t";
case SetType: return "Table_t"; case SetType: return "Table_t";
case ChannelType: return "Channel_t*";
case TableType: return "Table_t"; case TableType: return "Table_t";
case FunctionType: { case FunctionType: {
auto fn = Match(t, FunctionType); auto fn = Match(t, FunctionType);
@ -298,7 +297,7 @@ CORD compile_type(type_t *t)
type_t *nonnull = Match(t, OptionalType)->type; type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) { switch (nonnull->tag) {
case CStringType: case FunctionType: case ClosureType: case CStringType: case FunctionType: case ClosureType:
case PointerType: case EnumType: case ChannelType: case PointerType: case EnumType:
return compile_type(nonnull); return compile_type(nonnull);
case TextType: case TextType:
return Match(nonnull, TextType)->lang ? compile_type(nonnull) : "OptionalText_t"; return Match(nonnull, TextType)->lang ? compile_type(nonnull) : "OptionalText_t";
@ -436,7 +435,7 @@ CORD check_none(type_t *t, CORD value)
{ {
t = Match(t, OptionalType)->type; t = Match(t, OptionalType)->type;
if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType
|| t->tag == ChannelType || t == THREAD_TYPE) || t == THREAD_TYPE)
return CORD_all("(", value, " == NULL)"); return CORD_all("(", value, " == NULL)");
else if (t == MATCH_TYPE) else if (t == MATCH_TYPE)
return CORD_all("((", value, ").index.small == 0)"); return CORD_all("((", value, ").index.small == 0)");
@ -1582,7 +1581,6 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color)
case TextType: return CORD_asprintf("Text$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case TextType: return CORD_asprintf("Text$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
case ArrayType: return CORD_asprintf("Array$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); 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 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 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 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)); case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
@ -1988,7 +1986,6 @@ CORD compile_none(type_t *t)
case ArrayType: return "NONE_ARRAY"; case ArrayType: return "NONE_ARRAY";
case TableType: return "NONE_TABLE"; case TableType: return "NONE_TABLE";
case SetType: return "NONE_TABLE"; case SetType: return "NONE_TABLE";
case ChannelType: return "NULL";
case TextType: return "NONE_TEXT"; case TextType: return "NONE_TEXT";
case CStringType: return "NULL"; case CStringType: return "NULL";
case MomentType: return "NONE_MOMENT"; case MomentType: return "NONE_MOMENT";
@ -2127,6 +2124,9 @@ CORD compile(env_t *env, ast_t *ast)
else else
return CORD_all("stack(", compile(env, subject), ")"); return CORD_all("stack(", compile(env, subject), ")");
} }
case Mutexed: {
return CORD_all("mutexed(", compile(env, Match(ast, Mutexed)->value), ")");
}
case Optional: { case Optional: {
ast_t *value = Match(ast, Optional)->value; ast_t *value = Match(ast, Optional)->value;
CORD value_code = compile(env, value); CORD value_code = compile(env, value);
@ -2578,20 +2578,6 @@ CORD compile(env_t *env, ast_t *ast)
return code; return code;
} }
} }
case Channel: {
auto chan = Match(ast, Channel);
type_t *item_t = parse_type_ast(env, chan->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.");
if (chan->max_size) {
CORD max_size = compile(env, chan->max_size);
if (!promote(env, chan->max_size, &max_size, get_type(env, chan->max_size), INT_TYPE))
code_err(chan->max_size, "This value must be an integer, not %T", get_type(env, chan->max_size));
return CORD_all("Channel$new(", max_size, ")");
} else {
return "Channel$new(I(INT32_MAX))";
}
}
case Table: { case Table: {
auto table = Match(ast, Table); auto table = Match(ast, Table);
if (!table->entries) { if (!table->entries) {
@ -3092,41 +3078,6 @@ CORD compile(env_t *env, ast_t *ast)
", ", compile_type_info(env, self_value_t), ")"); ", ", compile_type_info(env, self_value_t), ")");
} else code_err(ast, "There is no '%s' method for tables", call->name); } 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", type_size(item_t));
arg_t *front_default_end = new(arg_t, .name="front", .type=Type(BoolType), .default_val=FakeAST(Bool, false));
arg_t *front_default_start = new(arg_t, .name="front", .type=Type(BoolType), .default_val=FakeAST(Bool, true));
if (streq(call->name, "give")) {
self = compile_to_pointer_depth(env, call->self, 0, false);
arg_t *arg_spec = new(arg_t, .name="item", .type=item_t, .next=front_default_end);
return CORD_all("Channel$give_value(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ",
padded_item_size, ")");
} else if (streq(call->name, "give_all")) {
self = compile_to_pointer_depth(env, call->self, 0, false);
arg_t *arg_spec = new(arg_t, .name="to_give", .type=Type(ArrayType, .item_type=item_t), .next=front_default_end);
return CORD_all("Channel$give_all(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ",
padded_item_size, ")");
} else if (streq(call->name, "get")) {
self = compile_to_pointer_depth(env, call->self, 0, false);
arg_t *arg_spec = front_default_start;
return CORD_all("Channel$get_value(", self, ", ", compile_arguments(env, ast, arg_spec, call->args),
", ", compile_type(item_t), ", ", padded_item_size, ")");
} else if (streq(call->name, "peek")) {
self = compile_to_pointer_depth(env, call->self, 0, false);
arg_t *arg_spec = front_default_start;
return CORD_all("Channel$peek_value(", self, ", ", compile_arguments(env, ast, arg_spec, call->args),
", ", compile_type(item_t), ")");
} else if (streq(call->name, "clear")) {
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")) {
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: { case TableType: {
auto table = Match(self_value_t, TableType); auto table = Match(self_value_t, TableType);
if (streq(call->name, "get")) { if (streq(call->name, "get")) {
@ -3599,11 +3550,6 @@ CORD compile(env_t *env, ast_t *ast)
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)"); return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)");
code_err(ast, "There is no %s field on arrays", f->field); code_err(ast, "There is no %s field on arrays", f->field);
} }
case ChannelType: {
if (streq(f->field, "max_size"))
return CORD_all("Int64_to_Int((", compile_to_pointer_depth(env, f->fielded, 0, false), ")->max_size)");
code_err(ast, "There is no %s field on arrays", f->field);
}
case SetType: { case SetType: {
if (streq(f->field, "items")) if (streq(f->field, "items"))
return CORD_all("ARRAY_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)"); return CORD_all("ARRAY_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)");
@ -3842,10 +3788,6 @@ CORD compile_type_info(env_t *env, type_t *t)
type_t *item_type = Match(t, SetType)->item_type; type_t *item_type = Match(t, SetType)->item_type;
return CORD_all("Set$info(", compile_type_info(env, item_type), ")"); return CORD_all("Set$info(", compile_type_info(env, item_type), ")");
} }
case ChannelType: {
type_t *item_t = Match(t, ChannelType)->item_type;
return CORD_asprintf("Channel$info(%r)", compile_type_info(env, item_t));
}
case TableType: { case TableType: {
auto table = Match(t, TableType); auto table = Match(t, TableType);
type_t *key_type = table->key_type; type_t *key_type = table->key_type;

View File

@ -21,7 +21,6 @@ Information about Tomo's built-in types can be found here:
- [Arrays](arrays.md) - [Arrays](arrays.md)
- [Booleans](booleans.md) - [Booleans](booleans.md)
- [Bytes](bytes.md) - [Bytes](bytes.md)
- [Channels](channels.md)
- [Moment](moments.md) - [Moment](moments.md)
- [Enums](enums.md) - [Enums](enums.md)
- [Floating point numbers](nums.md) - [Floating point numbers](nums.md)

View File

@ -1,177 +0,0 @@
# Channels
Channels are a thread-safe message queue for communicating between threads,
although they can also be used as a general-purpose queue.
## Syntax
The syntax to create a channel is `|:T|`, where `T` is the type that will be
passed through the channel. You can also specify a maximum size for the
channel, which will cause giving to block until the recipient has gotten from
the channel if the maximum size is reached.
```tomo
channel := |:Int|
channel:give(10)
channel:give(20)
>> channel:get()
= 10
>> channel:get()
= 20
small_channel := |:Int; max_size=5|
```
## Channel Methods
### `give`
**Description:**
Adds an item to the channel.
**Signature:**
```tomo
func give(channel:|T|, item: T, front: Bool = no -> Void)
```
**Parameters:**
- `channel`: The channel to which the item will be added.
- `item`: The item to add to the channel.
- `front`: Whether to put the item at the front of the channel (as opposed to the back).
**Returns:**
Nothing.
**Example:**
```tomo
>> channel:give("Hello")
```
---
### `give_all`
**Description:**
Adds multiple items to the channel.
**Signature:**
```tomo
func give_all(channel:|T|, items: [T], front: Bool = no -> Void)
```
**Parameters:**
- `channel`: The channel to which the items will be added.
- `items`: The array of items to add to the channel.
- `front`: Whether to put the item at the front of the channel (as opposed to the back).
**Returns:**
Nothing.
**Example:**
```tomo
>> channel:give_all([1, 2, 3])
```
---
### `get`
**Description:**
Removes and returns an item from the channel. If the channel is empty, it waits until an item is available.
**Signature:**
```tomo
func get(channel:|T|, front: Bool = yes -> T)
```
**Parameters:**
- `channel`: The channel from which to remove an item.
- `front`: Whether to put the item at the front of the channel (as opposed to the back).
**Returns:**
The item removed from the channel.
**Example:**
```tomo
>> channel:peek()
= "Hello"
```
---
### `peek`
**Description:**
Returns the next item that will come out of the channel, but without removing
it. If the channel is empty, it waits until an item is available.
**Signature:**
```tomo
func peek(channel:|T|, front: Bool = yes -> T)
```
**Parameters:**
- `channel`: The channel from which to remove an item.
- `front`: Whether to put the item at the front of the channel (as opposed to the back).
**Returns:**
The item removed from the channel.
**Example:**
```tomo
>> channel:get()
= "Hello"
```
---
### `clear`
**Description:**
Removes all items from the channel.
**Signature:**
```tomo
func clear(channel:|T| -> Void)
```
**Parameters:**
- `channel`: The mutable reference to the channel.
**Returns:**
Nothing.
**Example:**
```tomo
>> channel:clear()
```
---
### `view`
**Description:**
Returns a list of all items currently in the channel without removing them.
**Signature:**
```tomo
func channel:view(->[T])
```
**Parameters:**
- `channel`: The channel to view.
**Returns:**
An array of items currently in the channel.
**Example:**
```tomo
>> channel:view()
= [1, 2, 3]
```

View File

@ -127,9 +127,9 @@ for line in lines:
## Implementation Notes ## Implementation Notes
The implementation of optional types is highly efficient and has no memory The implementation of optional types is highly efficient and has no memory
overhead for pointers, collection types (arrays, sets, tables, channels), overhead for pointers, collection types (arrays, sets, tables), booleans,
booleans, texts, enums, nums, or integers (`Int` type only). This is done by texts, enums, nums, or integers (`Int` type only). This is done by using
using carefully chosen values, such as `0` for pointers, `2` for booleans, or a carefully chosen values, such as `0` for pointers, `2` for booleans, or a
negative length for arrays. However, for fixed-size integers (`Int64`, `Int32`, negative length for arrays. However, for fixed-size integers (`Int64`, `Int32`,
`Int16`, and `Int8`), bytes, and structs, an additional byte is required for `Int16`, and `Int8`), bytes, and structs, an additional byte is required for
out-of-band information about whether the value is none or not. out-of-band information about whether the value is none or not.

View File

@ -2,7 +2,7 @@
Tomo supports POSIX threads (pthreads) through the `Thread` type. The Tomo supports POSIX threads (pthreads) through the `Thread` type. The
recommended practice is to have each thread interact with other threads only recommended practice is to have each thread interact with other threads only
through thread-safe Channels with no other shared data. through mutex-guarded datastructures.
## Thread Methods ## Thread Methods

40
parse.c
View File

@ -52,7 +52,7 @@ int op_tightness[] = {
static const char *keywords[] = { static const char *keywords[] = {
"yes", "xor", "while", "when", "use", "unless", "struct", "stop", "skip", "return", "yes", "xor", "while", "when", "use", "unless", "struct", "stop", "skip", "return",
"or", "not", "no", "mod1", "mod", "pass", "lang", "inline", "in", "if", "or", "not", "no", "mutexed", "mod1", "mod", "pass", "lang", "inline", "in", "if",
"func", "for", "extern", "enum", "else", "do", "deserialize", "defer", "and", "none", "func", "for", "extern", "enum", "else", "do", "deserialize", "defer", "and", "none",
"_min_", "_max_", NULL, "_min_", "_max_", NULL,
}; };
@ -82,7 +82,6 @@ static ast_t *parse_optional_conditional_suffix(parse_ctx_t *ctx, ast_t *stmt);
static ast_t *parse_optional_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); static arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos);
static type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_channel_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_func_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_func_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos);
@ -94,7 +93,6 @@ static PARSER(parse_array);
static PARSER(parse_assignment); static PARSER(parse_assignment);
static PARSER(parse_block); static PARSER(parse_block);
static PARSER(parse_bool); static PARSER(parse_bool);
static PARSER(parse_channel);
static PARSER(parse_declaration); static PARSER(parse_declaration);
static PARSER(parse_defer); static PARSER(parse_defer);
static PARSER(parse_do); static PARSER(parse_do);
@ -113,6 +111,7 @@ static PARSER(parse_int);
static PARSER(parse_moment); static PARSER(parse_moment);
static PARSER(parse_lambda); static PARSER(parse_lambda);
static PARSER(parse_lang_def); static PARSER(parse_lang_def);
static PARSER(parse_mutexed);
static PARSER(parse_namespace); static PARSER(parse_namespace);
static PARSER(parse_negative); static PARSER(parse_negative);
static PARSER(parse_not); static PARSER(parse_not);
@ -616,15 +615,6 @@ type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos) {
return NewTypeAST(ctx->file, start, pos, ArrayTypeAST, .item=type); 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) { type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos; const char *start = pos;
bool is_stack; bool is_stack;
@ -667,7 +657,6 @@ type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) {
bool success = (false bool success = (false
|| (type=parse_pointer_type(ctx, pos)) || (type=parse_pointer_type(ctx, pos))
|| (type=parse_array_type(ctx, pos)) || (type=parse_array_type(ctx, pos))
|| (type=parse_channel_type(ctx, pos))
|| (type=parse_table_type(ctx, pos)) || (type=parse_table_type(ctx, pos))
|| (type=parse_set_type(ctx, pos)) || (type=parse_set_type(ctx, pos))
|| (type=parse_type_name(ctx, pos)) || (type=parse_type_name(ctx, pos))
@ -751,21 +740,6 @@ 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");
ast_t *max_size = NULL;
if (match(&pos, ";")) {
whitespace(&pos);
const char *attr_start = pos;
if (match_word(&pos, "max_size") && match(&pos, "="))
max_size = expect(ctx, attr_start, &pos, parse_int, "I expected a maximum size 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, .max_size=max_size);
}
PARSER(parse_array) { PARSER(parse_array) {
const char *start = pos; const char *start = pos;
if (!match(&pos, "[")) return NULL; if (!match(&pos, "[")) return NULL;
@ -1289,6 +1263,14 @@ PARSER(parse_stack_reference) {
return ast; return ast;
} }
PARSER(parse_mutexed) {
const char *start = pos;
if (!match_word(&pos, "mutexed")) return NULL;
spaces(&pos);
ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this 'mutexed'");
return NewAST(ctx->file, start, pos, Mutexed, .value=val);
}
PARSER(parse_not) { PARSER(parse_not) {
const char *start = pos; const char *start = pos;
if (!match_word(&pos, "not")) return NULL; if (!match_word(&pos, "not")) return NULL;
@ -1633,7 +1615,6 @@ PARSER(parse_term_no_suffix) {
|| (term=parse_deserialize(ctx, pos)) || (term=parse_deserialize(ctx, pos))
|| (term=parse_var(ctx, pos)) || (term=parse_var(ctx, pos))
|| (term=parse_array(ctx, pos)) || (term=parse_array(ctx, pos))
|| (term=parse_channel(ctx, pos))
|| (term=parse_reduction(ctx, pos)) || (term=parse_reduction(ctx, pos))
|| (term=parse_pass(ctx, pos)) || (term=parse_pass(ctx, pos))
|| (term=parse_defer(ctx, pos)) || (term=parse_defer(ctx, pos))
@ -1641,6 +1622,7 @@ PARSER(parse_term_no_suffix) {
|| (term=parse_stop(ctx, pos)) || (term=parse_stop(ctx, pos))
|| (term=parse_return(ctx, pos)) || (term=parse_return(ctx, pos))
|| (term=parse_not(ctx, pos)) || (term=parse_not(ctx, pos))
|| (term=parse_mutexed(ctx, pos))
|| (term=parse_extern(ctx, pos)) || (term=parse_extern(ctx, pos))
|| (term=parse_inline_c(ctx, pos)) || (term=parse_inline_c(ctx, pos))
); );

View File

@ -19,7 +19,6 @@ some common functionality.
- Arrays: [header](stdlib/arrays.h), [implementation](stdlib/arrays.c) - Arrays: [header](stdlib/arrays.h), [implementation](stdlib/arrays.c)
- Bools: [header](stdlib/bools.h), [implementation](stdlib/bools.c) - Bools: [header](stdlib/bools.h), [implementation](stdlib/bools.c)
- Bytes: [header](stdlib/bytes.h), [implementation](stdlib/bytes.c) - Bytes: [header](stdlib/bytes.h), [implementation](stdlib/bytes.c)
- Channels: [header](stdlib/channels.h), [implementation](stdlib/channels.c)
- C Strings: [header](stdlib/c_strings.h), [implementation](stdlib/c_strings.c) - C Strings: [header](stdlib/c_strings.h), [implementation](stdlib/c_strings.c)
- Files (used internally only): [header](stdlib/files.h), [implementation](stdlib/files.c) - Files (used internally only): [header](stdlib/files.h), [implementation](stdlib/files.c)
- Functiontype: [header](stdlib/functiontype.h), [implementation](stdlib/functiontype.c) - Functiontype: [header](stdlib/functiontype.h), [implementation](stdlib/functiontype.c)

View File

@ -1,157 +0,0 @@
// Functions that operate on channels
#include <ctype.h>
#include <err.h>
#include <gc.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/param.h>
#include "arrays.h"
#include "metamethods.h"
#include "integers.h"
#include "siphash.h"
#include "text.h"
#include "types.h"
#include "util.h"
public Channel_t *Channel$new(Int_t max_size)
{
if (Int$compare_value(max_size, I_small(0)) <= 0)
fail("Cannot create a channel with a size less than one: %ld", max_size);
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;
channel->max_size = Int_to_Int64(max_size, false);
return channel;
}
public void Channel$give(Channel_t *channel, const void *item, bool front, int64_t padded_item_size)
{
(void)pthread_mutex_lock(&channel->mutex);
while (channel->items.length >= channel->max_size)
pthread_cond_wait(&channel->cond, &channel->mutex);
Int_t index = front ? I_small(1) : I_small(0);
Array$insert(&channel->items, item, index, padded_item_size);
(void)pthread_mutex_unlock(&channel->mutex);
(void)pthread_cond_signal(&channel->cond);
}
public void Channel$give_all(Channel_t *channel, Array_t to_give, bool front, int64_t padded_item_size)
{
if (to_give.length == 0) return;
(void)pthread_mutex_lock(&channel->mutex);
Int_t index = front ? I_small(1) : I_small(0);
if (channel->items.length + to_give.length >= channel->max_size) {
for (int64_t i = 0; i < to_give.length; i++) {
while (channel->items.length >= channel->max_size)
pthread_cond_wait(&channel->cond, &channel->mutex);
Array$insert(&channel->items, to_give.data + i*to_give.stride, index, padded_item_size);
}
} else {
Array$insert_all(&channel->items, to_give, index, padded_item_size);
}
(void)pthread_mutex_unlock(&channel->mutex);
(void)pthread_cond_signal(&channel->cond);
}
public void Channel$get(Channel_t *channel, void *out, bool front, 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 + channel->items.stride * (front ? 0 : channel->items.length-1), (size_t)(item_size));
Int_t index = front ? I_small(1) : Int64_to_Int(channel->items.length);
Array$remove_at(&channel->items, index, I_small(1), padded_item_size);
(void)pthread_mutex_unlock(&channel->mutex);
(void)pthread_cond_signal(&channel->cond);
}
public void Channel$peek(Channel_t *channel, void *out, bool front, int64_t item_size)
{
(void)pthread_mutex_lock(&channel->mutex);
while (channel->items.length == 0)
pthread_cond_wait(&channel->cond, &channel->mutex);
int64_t index = front ? 0 : channel->items.length-1;
memcpy(out, channel->items.data + channel->items.stride*index, (size_t)(item_size));
(void)pthread_mutex_unlock(&channel->mutex);
(void)pthread_cond_signal(&channel->cond);
}
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);
(void)pthread_cond_signal(&channel->cond);
}
PUREFUNC public uint64_t Channel$hash(const void *channel, const TypeInfo_t *type)
{
(void)type;
return siphash24(*(void**)channel, sizeof(Channel_t*));
}
PUREFUNC public int32_t Channel$compare(const void *x, const void *y, const TypeInfo_t*)
{
return (*(Channel_t**)x > *(Channel_t**)y) - (*(Channel_t**)x < *(Channel_t**)y);
}
PUREFUNC public bool Channel$equal(const void *x, const void *y, const TypeInfo_t*)
{
return (*(void**)x == *(void**)y);
}
public Text_t Channel$as_text(const void *channel, bool colorize, const TypeInfo_t *type)
{
const TypeInfo_t *item_type = type->ChannelInfo.item;
if (!channel) {
Text_t typename = generic_as_text(NULL, false, item_type);
return Text$concat(colorize ? Text("\x1b[34;1m|:") : Text("|:"), typename, colorize ? Text("|\x1b[m") : Text("|"));
}
Text_t typename = generic_as_text(NULL, false, item_type);
return Text$concat(
colorize ? Text("\x1b[34;1m|:") : Text("|:"),
typename,
Text("|<"),
Int64$hex(*(int64_t*)channel, I_small(0), true, true),
colorize ? Text(">\x1b[m") : Text(">")
);
}
public PUREFUNC bool Channel$is_none(const void *obj, const TypeInfo_t*)
{
return *(void**)obj == NULL;
}
public void Channel$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type)
{
Channel_t *channel = (Channel_t*)obj;
Array$serialize(&channel->items, out, pointers, Array$info(type->ChannelInfo.item));
Int64$serialize(&channel->max_size, out, pointers, &Int64$info);
}
public void Channel$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type)
{
Channel_t *channel = new(Channel_t);
channel->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
channel->cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
Array$deserialize(in, &channel->items, pointers, Array$info(type->ChannelInfo.item));
Int64$deserialize(in, &channel->max_size, pointers, &Int64$info);
*(Channel_t**)outval = channel;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -1,45 +0,0 @@
#pragma once
// Functions that operate on channels (thread-safe arrays)
#include <stdbool.h>
#include "datatypes.h"
#include "types.h"
#include "util.h"
Channel_t *Channel$new(Int_t max_size);
void Channel$give(Channel_t *channel, const void *item, bool front, int64_t padded_item_size);
#define Channel$give_value(channel, item, front, padded_item_size) \
({ __typeof(item) _item = item; Channel$give(channel, &_item, front, padded_item_size); })
void Channel$give_all(Channel_t *channel, Array_t to_give, bool front, int64_t padded_item_size);
void Channel$get(Channel_t *channel, void *out, bool front, int64_t item_size, int64_t padded_item_size);
#define Channel$get_value(channel, front, t, padded_item_size) \
({ t _val; Channel$get(channel, &_val, front, sizeof(t), padded_item_size); _val; })
void Channel$peek(Channel_t *channel, void *out, bool front, int64_t item_size);
#define Channel$peek_value(channel, front, t) ({ t _val; Channel$peek(channel, &_val, front, sizeof(t)); _val; })
void Channel$clear(Channel_t *channel);
Array_t Channel$view(Channel_t *channel);
PUREFUNC uint64_t Channel$hash(const void *channel, const TypeInfo_t *type);
PUREFUNC int32_t Channel$compare(const void *x, const void *y, const TypeInfo_t *type);
PUREFUNC bool Channel$equal(const void *x, const void *y, const TypeInfo_t *type);
Text_t Channel$as_text(const void *channel, bool colorize, const TypeInfo_t *type);
PUREFUNC bool Channel$is_none(const void *obj, const TypeInfo_t*);
void Channel$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t*);
void Channel$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t*);
#define Channel$metamethods ((metamethods_t){ \
.as_text=Channel$as_text, \
.compare=Channel$compare, \
.equal=Channel$equal, \
.hash=Channel$hash, \
.is_none=Channel$is_none, \
.serialize=Channel$serialize, \
.deserialize=Channel$deserialize, \
})
#define Channel$info(item_info) &((TypeInfo_t){.size=sizeof(Channel_t), .align=__alignof__(Channel_t), \
.tag=ChannelInfo, .ChannelInfo.item=item_info, \
.metamethods=Channel$metamethods})
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -66,13 +66,6 @@ typedef struct Range_s {
Int_t first, last, step; Int_t first, last, step;
} Range_t; } Range_t;
typedef struct {
Array_t items;
pthread_mutex_t mutex;
pthread_cond_t cond;
int64_t max_size;
} Channel_t;
enum text_type { TEXT_SHORT_ASCII, TEXT_ASCII, TEXT_SHORT_GRAPHEMES, TEXT_GRAPHEMES, TEXT_SUBTEXT }; enum text_type { TEXT_SHORT_ASCII, TEXT_ASCII, TEXT_SHORT_GRAPHEMES, TEXT_GRAPHEMES, TEXT_SUBTEXT };
typedef struct Text_s { typedef struct Text_s {

View File

@ -5,7 +5,6 @@
#include "arrays.h" #include "arrays.h"
#include "bools.h" #include "bools.h"
#include "channels.h"
#include "functiontype.h" #include "functiontype.h"
#include "integers.h" #include "integers.h"
#include "metamethods.h" #include "metamethods.h"

View File

@ -6,7 +6,6 @@
#include "arrays.h" #include "arrays.h"
#include "bools.h" #include "bools.h"
#include "bytes.h" #include "bytes.h"
#include "channels.h"
#include "functiontype.h" #include "functiontype.h"
#include "integers.h" #include "integers.h"
#include "metamethods.h" #include "metamethods.h"

View File

@ -621,4 +621,34 @@ public void sleep_num(double seconds)
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
static void use_mutexed(Closure_t fn, void *userdata)
{
void (*call)(void*, void*) = fn.fn;
struct data_t {
pthread_mutex_t mutex;
char item[0] __attribute__ ((aligned (8)));
};
pthread_mutex_t *mutex = &((struct data_t*)userdata)->mutex;
pthread_mutex_lock(mutex);
void *mutexed_item = (void*)((struct data_t*)userdata)->item;
call(mutexed_item, fn.userdata);
pthread_mutex_unlock(mutex);
}
public Closure_t _mutexed(const void *item, size_t size)
{
struct data_t {
pthread_mutex_t mutex;
char item[size] __attribute__ ((aligned (8)));
};
struct data_t *userdata = GC_MALLOC(sizeof(struct data_t));
pthread_mutex_init(&userdata->mutex, NULL);
memcpy(userdata->item, item, size);
return (Closure_t){
.fn=(void*)use_mutexed,
.userdata=(void*)userdata,
};
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -42,5 +42,7 @@ Closure_t spawn(Closure_t fn);
bool pop_flag(char **argv, int *i, const char *flag, Text_t *result); bool pop_flag(char **argv, int *i, const char *flag, Text_t *result);
void print_stack_trace(FILE *out, int start, int stop); void print_stack_trace(FILE *out, int start, int stop);
void sleep_num(double seconds); void sleep_num(double seconds);
public Closure_t _mutexed(const void *item, size_t size);
#define mutexed(item) _mutexed((__typeof(item)[1]){item}, sizeof(item))
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -5,7 +5,6 @@
#include "arrays.h" #include "arrays.h"
#include "bools.h" #include "bools.h"
#include "channels.h"
#include "functiontype.h" #include "functiontype.h"
#include "metamethods.h" #include "metamethods.h"
#include "optionals.h" #include "optionals.h"

View File

@ -11,7 +11,6 @@
#include "bools.h" #include "bools.h"
#include "bytes.h" #include "bytes.h"
#include "c_strings.h" #include "c_strings.h"
#include "channels.h"
#include "datatypes.h" #include "datatypes.h"
#include "enums.h" #include "enums.h"
#include "functiontype.h" #include "functiontype.h"

View File

@ -29,7 +29,7 @@ struct TypeInfo_s {
int64_t size, align; int64_t size, align;
metamethods_t metamethods; metamethods_t metamethods;
struct { // Anonymous tagged union for convenience struct { // Anonymous tagged union for convenience
enum { OpaqueInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo, enum { OpaqueInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, TableInfo, FunctionInfo,
OptionalInfo, TypeInfoInfo } tag; OptionalInfo, TypeInfoInfo } tag;
union { union {
struct {} OpaqueInfo; struct {} OpaqueInfo;
@ -42,7 +42,7 @@ struct TypeInfo_s {
} TextInfo; } TextInfo;
struct { struct {
const TypeInfo_t *item; const TypeInfo_t *item;
} ArrayInfo, ChannelInfo; } ArrayInfo;
struct { struct {
const TypeInfo_t *key, *value; const TypeInfo_t *key, *value;
} TableInfo; } TableInfo;

View File

@ -61,12 +61,6 @@ func maybe_c_string(should_i:Bool->CString?):
else: else:
return none return none
func maybe_channel(should_i:Bool->|Int|?):
if should_i:
return |:Int|?
else:
return none
func maybe_thread(should_i:Bool->Thread?): func maybe_thread(should_i:Bool->Thread?):
if should_i: if should_i:
return Thread.new(func(): pass) return Thread.new(func(): pass)
@ -244,19 +238,6 @@ func main():
fail("Truthy: $nope") fail("Truthy: $nope")
else: !! Falsey: $nope else: !! Falsey: $nope
do:
!! ...
!! Channels:
>> yep := maybe_channel(yes)
# No "=" test here because channels use addresses in the text version
>> nope := maybe_channel(no)
= none : |:Int|?
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do: do:
!! ... !! ...
!! Threads: !! Threads:

View File

@ -1,66 +1,74 @@
enum Job(Increment(x:Int), Decrement(x:Int)) enum Job(Increment(x:Int), Decrement(x:Int))
func main(): func main():
do: do:
>> channel := |:Int| >> with_nums := mutexed [10, 20, 30]
>> channel:give(10) with_nums(func(nums:&[Int]):
>> channel:give(20) >> nums[]
>> channel:give(30) = [10, 20, 30]
>> channel:view() nums:insert(40)
= [10, 20, 30] )
>> channel:peek() with_nums(func(nums:&[Int]):
= 10 >> nums[]
>> channel:peek(front=no) = [10, 20, 30, 40]
= 30 )
>> channel:give(-10, front=yes)
>> channel:view()
= [-10, 10, 20, 30]
jobs := |:Job; max_size=2| with_jobs := mutexed [Job.Increment(5)]
>> jobs:give(Increment(5)) enqueue_job := func(job:Job):
>> jobs:peek() with_jobs(func(jobs:&[Job]): jobs:insert(job))
= Job.Increment(5) dequeue_job := func():
job := @none:Job
with_jobs(func(jobs:&[Job]): job[] = jobs:pop(1))
job[]
with_results := mutexed [:Int]
enqueue_result := func(result:Int):
with_results(func(results:&[Int]): results:insert(result))
dequeue_result := func():
result := @none:Int
repeat:
with_results(func(results:&[Int]): result[] = results:pop(1))
stop if result[]
sleep(0.00001)
result[]!
results := |:Int; max_size|
>> thread := Thread.new(func(): >> thread := Thread.new(func():
!! In another thread! !! In another thread!
repeat: repeat:
>> got := jobs:get() job := dequeue_job() or stop
when got is Increment(x): when job is Increment(x):
>> results:give(x+1) enqueue_result(x + 1)
is Decrement(x): is Decrement(x):
>> results:give(x-1) enqueue_result(x - 1)
) )
>> jobs:give(Decrement(100)) enqueue_job(Decrement(100))
>> jobs:give(Decrement(100)) enqueue_job(Decrement(200))
>> jobs:give(Decrement(100)) enqueue_job(Decrement(300))
>> jobs:give(Decrement(100)) enqueue_job(Decrement(400))
>> jobs:give(Decrement(100)) enqueue_job(Decrement(500))
>> jobs:give(Decrement(100)) enqueue_job(Decrement(600))
>> results:get() >> enqueue_job(Increment(1000))
>> dequeue_result()
= 6 = 6
>> dequeue_result()
>> jobs:give(Increment(1000))
>> results:get()
= 99 = 99
>> results:get() >> dequeue_result()
= 99 = 199
>> results:get() >> dequeue_result()
= 99 = 299
>> results:get() >> dequeue_result()
= 99 = 399
>> results:get() >> dequeue_result()
= 99 = 499
>> results:get() >> dequeue_result()
= 99 = 599
>> results:get() >> dequeue_result()
= 1001 = 1001
!! Canceling... !! Canceling...

View File

@ -71,17 +71,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
type_size(item_t), ARRAY_MAX_STRIDE); type_size(item_t), ARRAY_MAX_STRIDE);
return Type(SetType, .item_type=item_t); 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 (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.",
type_size(item_t), ARRAY_MAX_STRIDE);
return Type(ChannelType, .item_type=item_t);
}
case TableTypeAST: { case TableTypeAST: {
auto table_type = Match(ast, TableTypeAST); auto table_type = Match(ast, TableTypeAST);
type_ast_t *key_type_ast = table_type->key; type_ast_t *key_type_ast = table_type->key;
@ -672,11 +661,6 @@ 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."); 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); 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: { case Table: {
auto table = Match(ast, Table); auto table = Match(ast, Table);
type_t *key_type = NULL, *value_type = NULL; type_t *key_type = NULL, *value_type = NULL;
@ -787,7 +771,6 @@ type_t *get_type(env_t *env, ast_t *ast)
} }
case FunctionCall: { case FunctionCall: {
auto call = Match(ast, FunctionCall); auto call = Match(ast, FunctionCall);
type_t *fn_type_t = get_type(env, call->fn); type_t *fn_type_t = get_type(env, call->fn);
if (!fn_type_t) if (!fn_type_t)
code_err(call->fn, "I couldn't find this function"); code_err(call->fn, "I couldn't find this function");
@ -858,15 +841,6 @@ type_t *get_type(env_t *env, ast_t *ast)
else if (streq(call->name, "without")) return self_value_t; else if (streq(call->name, "without")) return self_value_t;
else code_err(ast, "There is no '%s' method for sets", call->name); else code_err(ast, "There is no '%s' method for sets", call->name);
} }
case ChannelType: {
if (streq(call->name, "clear")) return Type(VoidType);
else if (streq(call->name, "get")) return Match(self_value_t, ChannelType)->item_type;
else if (streq(call->name, "give")) return Type(VoidType);
else if (streq(call->name, "give_all")) return Type(VoidType);
else if (streq(call->name, "peek")) return Match(self_value_t, ChannelType)->item_type;
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: { case TableType: {
auto table = Match(self_value_t, TableType); auto table = Match(self_value_t, TableType);
if (streq(call->name, "bump")) return Type(VoidType); if (streq(call->name, "bump")) return Type(VoidType);
@ -976,6 +950,12 @@ type_t *get_type(env_t *env, ast_t *ast)
} }
code_err(ast, "I only know how to get 'not' of boolean, numeric, and optional pointer types, not %T", t); code_err(ast, "I only know how to get 'not' of boolean, numeric, and optional pointer types, not %T", t);
} }
case Mutexed: {
type_t *item_type = get_type(env, Match(ast, Mutexed)->value);
type_t *user_thunk = Type(ClosureType, Type(FunctionType, .args=new(arg_t, .name="object", .type=Type(PointerType, .pointed=item_type, .is_stack=true)),
.ret=Type(VoidType)));
return Type(ClosureType, Type(FunctionType, .args=new(arg_t, .name="fn", .type=user_thunk), .ret=Type(VoidType)));
}
case BinaryOp: { case BinaryOp: {
auto binop = Match(ast, BinaryOp); auto binop = Match(ast, BinaryOp);
type_t *lhs_t = get_type(env, binop->lhs), type_t *lhs_t = get_type(env, binop->lhs),

37
types.c
View File

@ -37,10 +37,6 @@ CORD type_to_cord(type_t *t) {
auto array = Match(t, ArrayType); auto array = Match(t, ArrayType);
return CORD_asprintf("[%r]", type_to_cord(array->item_type)); 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: { case TableType: {
auto table = Match(t, TableType); auto table = Match(t, TableType);
if (table->default_value) if (table->default_value)
@ -245,7 +241,6 @@ PUREFUNC bool has_heap_memory(type_t *t)
{ {
switch (t->tag) { switch (t->tag) {
case ArrayType: return true; case ArrayType: return true;
case ChannelType: return true;
case TableType: return true; case TableType: return true;
case SetType: return true; case SetType: return true;
case PointerType: return true; case PointerType: return true;
@ -269,32 +264,6 @@ PUREFUNC bool has_heap_memory(type_t *t)
} }
} }
PUREFUNC 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 OptionalType: return can_send_over_channel(Match(t, OptionalType)->type);
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;
}
}
PUREFUNC bool has_stack_memory(type_t *t) PUREFUNC bool has_stack_memory(type_t *t)
{ {
switch (t->tag) { switch (t->tag) {
@ -523,7 +492,6 @@ PUREFUNC size_t type_size(type_t *t)
case TextType: return sizeof(Text_t); case TextType: return sizeof(Text_t);
case ArrayType: return sizeof(Array_t); case ArrayType: return sizeof(Array_t);
case SetType: return sizeof(Table_t); case SetType: return sizeof(Table_t);
case ChannelType: return sizeof(Channel_t*);
case TableType: return sizeof(Table_t); case TableType: return sizeof(Table_t);
case FunctionType: return sizeof(void*); case FunctionType: return sizeof(void*);
case ClosureType: return sizeof(struct {void *fn, *userdata;}); case ClosureType: return sizeof(struct {void *fn, *userdata;});
@ -607,7 +575,6 @@ PUREFUNC size_t type_align(type_t *t)
case TextType: return __alignof__(Text_t); case TextType: return __alignof__(Text_t);
case SetType: return __alignof__(Table_t); case SetType: return __alignof__(Table_t);
case ArrayType: return __alignof__(Array_t); case ArrayType: return __alignof__(Array_t);
case ChannelType: return __alignof__(Channel_t*);
case TableType: return __alignof__(Table_t); case TableType: return __alignof__(Table_t);
case FunctionType: return __alignof__(void*); case FunctionType: return __alignof__(void*);
case ClosureType: return __alignof__(struct {void *fn, *userdata;}); case ClosureType: return __alignof__(struct {void *fn, *userdata;});
@ -706,10 +673,6 @@ type_t *get_field_type(type_t *t, const char *field_name)
else if (streq(field_name, "microseconds")) return Type(IntType, .bits=TYPE_IBITS64); else if (streq(field_name, "microseconds")) return Type(IntType, .bits=TYPE_IBITS64);
return NULL; return NULL;
} }
case ChannelType: {
if (streq(field_name, "max_size")) return INT_TYPE;
return NULL;
}
default: return NULL; default: return NULL;
} }
} }

View File

@ -51,7 +51,6 @@ struct type_s {
MomentType, MomentType,
TextType, TextType,
ArrayType, ArrayType,
ChannelType,
SetType, SetType,
TableType, TableType,
FunctionType, FunctionType,
@ -85,7 +84,7 @@ struct type_s {
} TextType; } TextType;
struct { struct {
type_t *item_type; type_t *item_type;
} ArrayType, ChannelType; } ArrayType;
struct { struct {
type_t *item_type; type_t *item_type;
} SetType; } SetType;
@ -145,7 +144,6 @@ typedef enum {NUM_PRECISION_EQUAL, NUM_PRECISION_LESS, NUM_PRECISION_MORE, NUM_P
PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b); PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b);
PUREFUNC bool has_heap_memory(type_t *t); PUREFUNC bool has_heap_memory(type_t *t);
PUREFUNC bool has_stack_memory(type_t *t); PUREFUNC bool has_stack_memory(type_t *t);
PUREFUNC bool can_send_over_channel(type_t *t);
PUREFUNC bool can_promote(type_t *actual, type_t *needed); PUREFUNC bool can_promote(type_t *actual, type_t *needed);
PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t); PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t);
PUREFUNC bool is_int_type(type_t *t); PUREFUNC bool is_int_type(type_t *t);