Remove threads and mutexed data from the language in favor of a

module-based approach
This commit is contained in:
Bruce Hill 2025-03-31 02:11:03 -04:00
parent d3655740cc
commit 7a172be621
28 changed files with 23 additions and 713 deletions

View File

@ -47,7 +47,7 @@ LDLIBS=-lgc -lcord -lm -lunistring -lgmp
LIBTOMO_FLAGS=-shared
ifeq ($(OS),OpenBSD)
LDLIBS += -lpthread -lexecinfo
LDLIBS += -lexecinfo
endif
ifeq ($(OS),Darwin)
@ -107,7 +107,7 @@ clean:
examples: examples/base64/base64 examples/ini/ini examples/game/game \
examples/tomodeps/tomodeps examples/tomo-install/tomo-install examples/wrap/wrap examples/colorful/colorful
./build/tomo -qIL examples/time examples/commands examples/shell examples/base64 examples/log examples/ini examples/vectors examples/game \
examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap examples/pthreads examples/colorful
examples/http examples/tomodeps examples/tomo-install examples/wrap examples/pthreads examples/colorful
./build/tomo examples/learnxiny.tm
deps: check-gcc

View File

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

View File

@ -25,7 +25,6 @@ Information about Tomo's built-in types can be found here:
- [Floating point numbers](nums.md)
- [Integers](integers.md)
- [Languages](langs.md)
- [Mutexed Data](mutexed.md)
- [Paths](paths.md)
- [Random Number Generators](rng.md)
- [Sets](sets.md)
@ -33,7 +32,6 @@ Information about Tomo's built-in types can be found here:
- [Tables](tables.md)
- [Text](text.md)
- [Text Pattern Matching](patterns.md)
- [Threads](threads.md)
## Built-in Functions

View File

@ -1,34 +0,0 @@
# Mutexed Data
To serve the general case of synchronizing access to shared datastructures,
Tomo uses the `mutexed` keyword, which allocates a mutex and some heap memory
for a value. Access to the heap-allocated value's memory can only be obtained
by using a `holding` block. `holding` blocks ensure that the underlying mutex
is locked before entering the block and unlocked before leaving it (even if a
short-circuiting control flow statement like `return` or `stop` is used). Here
is a simple example:
```tomo
nums := mutexed [10, 20, 30]
>> nums
= mutexed [Int]<0x12345678> : mutexed([Int])
holding nums:
# Inside this block, the type of `nums` is `&[Int]`
>> nums
= &[10, 20, 30] : &[Int]
thread := Thread.new(func():
holding nums:
nums:insert(30)
)
holding nums:
nums:insert(40)
thread:join()
```
Without using a mutex, the code above could run into concurrency issues leading
to data corruption.

View File

@ -70,7 +70,6 @@ only 9 bytes for the whole thing!
## Unserializable Types
Unfortunately, not all types can be easily serialized. In particular,
`Thread`s, types, and functions cannot be serialized because their data
contents cannot be easily converted to portable byte arrays. All other
datatypes _can_ be serialized.
Unfortunately, not all types can be easily serialized. In particular, types and
functions cannot be serialized because their data contents cannot be easily
converted to portable byte arrays. All other datatypes _can_ be serialized.

View File

@ -1,93 +0,0 @@
# Threads
Tomo supports POSIX threads (pthreads) through the `Thread` type. The
recommended practice is to have each thread interact with other threads only
through [mutex-guarded datastructures](mutexed.md).
## Thread Methods
- [`func cancel(thread: Thread)`](#cancel)
- [`func detach(thread: Thread)`](#detach)
- [`func join(thread: Thread)`](#join)
- [`func new(fn: func(->Void) -> Thread)`](#new)
### `cancel`
Requests the cancellation of a specified thread.
```tomo
func cancel(thread: Thread)
```
- `thread`: The thread to cancel.
**Returns:**
Nothing.
**Example:**
```tomo
>> thread:cancel()
```
---
### `detach`
Detaches a specified thread, allowing it to run independently.
```tomo
func detach(thread: Thread)
```
- `thread`: The thread to detach.
**Returns:**
Nothing.
**Example:**
```tomo
>> thread:detach()
```
### `join`
Waits for a specified thread to terminate.
```tomo
func join(thread: Thread)
```
- `thread`: The thread to join.
**Returns:**
Nothing.
**Example:**
```tomo
>> thread:join()
```
---
### `new`
Creates a new thread to execute a specified function.
```tomo
func new(fn: func(->Void) -> Thread)
```
- `fn`: The function to be executed by the new thread.
**Returns:**
A new `Thread` object representing the created thread.
**Example:**
```tomo
>> jobs := |Int|
>> results := |Int|
>> thread := Thread.new(func():
repeat:
input := jobs:get()
results:give(input + 10
)
= Thread<0x12345678>
>> jobs:give(10)
>> results:get()
= 11
```

View File

@ -1,106 +0,0 @@
use <pthread.h>
struct Mutex(_mutex:@Memory):
func new(->Mutex):
return Mutex(
inline C : @Memory {
pthread_mutex_t *mutex = GC_MALLOC(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);
GC_register_finalizer(mutex, (void*)pthread_mutex_destroy, NULL, NULL, NULL);
mutex
}
)
func do_locked(m:Mutex, fn:func(); inline):
inline C {
pthread_mutex_lock((pthread_mutex_t*)_$m._mutex);
}
fn()
inline C {
pthread_mutex_unlock((pthread_mutex_t*)_$m._mutex);
}
struct ThreadCondition(_cond:@Memory):
func new(->ThreadCondition):
return ThreadCondition(
inline C : @Memory {
pthread_cond_t *cond = GC_MALLOC(sizeof(pthread_cond_t));
pthread_cond_init(cond, NULL);
GC_register_finalizer(cond, (void*)pthread_cond_destroy, NULL, NULL, NULL);
cond
}
)
func wait(c:ThreadCondition, m:Mutex; inline):
inline C {
pthread_cond_wait((pthread_cond_t*)_$c._cond, (pthread_mutex_t*)_$m._mutex);
}
func signal(c:ThreadCondition; inline):
inline C {
pthread_cond_signal((pthread_cond_t*)_$c._cond);
}
func broadcast(c:ThreadCondition; inline):
inline C {
pthread_cond_broadcast((pthread_cond_t*)_$c._cond);
}
struct Guard(mutex=Mutex.new(), cond=ThreadCondition.new()):
func guarded(g:Guard, fn:func(); inline):
g.mutex:do_locked(fn)
g.cond:signal()
func wait(g:Guard):
g.cond:wait(g.mutex)
struct PThread(_thread:@Memory):
func new(fn:func() -> PThread):
return PThread(
inline C : @Memory {
pthread_t *thread = GC_MALLOC(sizeof(pthread_t));
pthread_create(thread, NULL, _$fn.fn, _$fn.userdata);
thread
}
)
func join(t:PThread):
inline C {
pthread_join(*(pthread_t*)_$t._thread, NULL);
}
func cancel(t:PThread):
inline C {
pthread_cancel(*(pthread_t*)_$t._thread);
}
func detatch(t:PThread):
inline C {
pthread_detach(*(pthread_t*)_$t._thread);
}
func main():
g := Guard()
queue := @[10, 20]
t := PThread.new(func():
say("In another thread!")
item := @none:Int
while item[] != 30:
g:guarded(func():
while queue.length == 0:
g:wait()
item[] = queue[1]
queue:remove_at(1)
)
say("Processing: $item")
sleep(0.01)
say("All done!")
)
>> t
>> sleep(1)
>> g:guarded(func():
queue:insert(30)
)
>> t:join()

View File

@ -116,8 +116,6 @@ CORD ast_to_xml(ast_t *ast)
T(Not, "<Not>%r</Not>", 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(Mutexed, "<Mutexed>%r</Mutexed>", ast_to_xml(data.value))
T(Holding, "<Holding>%r%r</Holding>", ast_to_xml(data.mutexed), ast_to_xml(data.body))
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.item_type), ast_list_to_xml(data.items))
@ -192,7 +190,6 @@ CORD type_ast_to_xml(type_ast_t *t)
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(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type)
T(MutexedTypeAST, "<MutexedType>%r</MutexedType>", data.type)
#undef T
default: return CORD_EMPTY;
}

View File

@ -75,7 +75,6 @@ typedef enum {
TableTypeAST,
FunctionTypeAST,
OptionalTypeAST,
MutexedTypeAST,
} type_ast_e;
typedef struct tag_ast_s {
@ -114,7 +113,7 @@ struct type_ast_s {
} FunctionTypeAST;
struct {
type_ast_t *type;
} OptionalTypeAST, MutexedTypeAST;
} OptionalTypeAST;
} __data;
};
@ -126,7 +125,7 @@ typedef enum {
Path,
Declare, Assign,
BinaryOp, UpdateAssign,
Not, Negative, HeapAllocate, StackReference, Mutexed, Holding,
Not, Negative, HeapAllocate, StackReference,
Min, Max,
Array, Set, Table, TableEntry, Comprehension,
FunctionDef, Lambda, ConvertDef,
@ -194,10 +193,7 @@ struct ast_s {
} BinaryOp, UpdateAssign;
struct {
ast_t *value;
} Not, Negative, HeapAllocate, StackReference, Mutexed;
struct {
ast_t *mutexed, *body;
} Holding;
} Not, Negative, HeapAllocate, StackReference;
struct {
ast_t *lhs, *rhs, *key;
} Min, Max;

View File

@ -39,7 +39,7 @@ static CORD compile_string_literal(CORD literal);
CORD promote_to_optional(type_t *t, CORD code)
{
if (t == THREAD_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE || t == MATCH_TYPE) {
if (t == PATH_TYPE || t == PATH_TYPE_TYPE || t == MATCH_TYPE) {
return code;
} else if (t->tag == IntType) {
switch (Match(t, IntType)->bits) {
@ -228,18 +228,13 @@ static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, UpdateAssign)->rhs);
break;
}
case Not: case Negative: case HeapAllocate: case StackReference: case Mutexed: {
case Not: case Negative: case HeapAllocate: case StackReference: {
// UNSAFE:
ast_t *value = ast->__data.Not.value;
// END UNSAFE
add_closed_vars(closed_vars, enclosing_scope, env, value);
break;
}
case Holding: {
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Holding)->mutexed);
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Holding)->body);
break;
}
case Min: {
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->lhs);
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->rhs);
@ -501,8 +496,7 @@ PUREFUNC CORD compile_unsigned_type(type_t *t)
CORD compile_type(type_t *t)
{
if (t == THREAD_TYPE) return "Thread_t";
else if (t == RNG_TYPE) return "RNG_t";
if (t == RNG_TYPE) return "RNG_t";
else if (t == MATCH_TYPE) return "Match_t";
else if (t == PATH_TYPE) return "Path_t";
else if (t == PATH_TYPE_TYPE) return "PathType_t";
@ -512,7 +506,6 @@ CORD compile_type(type_t *t)
case AbortType: return "void";
case VoidType: return "void";
case MemoryType: return "void";
case MutexedType: return "MutexedData_t";
case BoolType: return "Bool_t";
case ByteType: return "Byte_t";
case CStringType: return "const char*";
@ -556,7 +549,7 @@ CORD compile_type(type_t *t)
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case CStringType: case FunctionType: case ClosureType: case MutexedType:
case CStringType: case FunctionType: case ClosureType:
case PointerType: case EnumType:
return compile_type(nonnull);
case TextType:
@ -565,8 +558,6 @@ CORD compile_type(type_t *t)
case ArrayType: case TableType: case SetType:
return CORD_all("Optional", compile_type(nonnull));
case StructType: {
if (nonnull == THREAD_TYPE)
return "Thread_t";
if (nonnull == MATCH_TYPE)
return "OptionalMatch_t";
if (nonnull == PATH_TYPE)
@ -689,7 +680,7 @@ CORD optional_into_nonnone(type_t *t, CORD value)
case IntType:
return CORD_all(value, ".value");
case StructType:
if (t == THREAD_TYPE || t == MATCH_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE)
if (t == MATCH_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE)
return value;
return CORD_all(value, ".value");
default:
@ -702,8 +693,7 @@ CORD check_none(type_t *t, CORD value)
t = Match(t, OptionalType)->type;
// NOTE: these use statement expressions ({...;}) because some compilers
// complain about excessive parens around equality comparisons
if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType
|| t == THREAD_TYPE)
if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType)
return CORD_all("({", value, " == NULL;})");
else if (t == MATCH_TYPE)
return CORD_all("({(", value, ").index.small == 0;})");
@ -729,8 +719,6 @@ CORD check_none(type_t *t, CORD value)
return CORD_all("(", value, ").is_none");
else if (t->tag == EnumType)
return CORD_all("({(", value, ").$tag == 0;})");
else if (t->tag == MutexedType)
return CORD_all("({", value, " == NULL;})");
print_err("Optional check not implemented for: ", type_to_str(t));
}
@ -1344,30 +1332,6 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
loop = CORD_all(loop, "\n", loop_ctx.stop_label, ":;");
return loop;
}
case Holding: {
ast_t *held = Match(ast, Holding)->mutexed;
type_t *held_type = get_type(env, held);
if (held_type->tag != MutexedType)
code_err(held, "This is a ", type_to_str(held_type), ", not a mutexed value");
CORD code = CORD_all(
"{ // Holding\n",
"MutexedData_t mutexed = ", compile(env, held), ";\n",
"pthread_mutex_lock(&mutexed->mutex);\n");
env_t *body_scope = fresh_scope(env);
body_scope->deferred = new(deferral_t, .defer_env=env,
.block=FakeAST(InlineCCode, .code="pthread_mutex_unlock(&mutexed->mutex);"),
.next=body_scope->deferred);
if (held->tag == Var) {
CORD held_var = CORD_all(Match(held, Var)->name, "$held");
set_binding(body_scope, Match(held, Var)->name,
Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true), held_var);
code = CORD_all(code, compile_declaration(Type(PointerType, .pointed=Match(held_type, MutexedType)->type), held_var),
" = (", compile_type(Type(PointerType, .pointed=Match(held_type, MutexedType)->type)), ")mutexed->data;\n");
}
return CORD_all(code, compile_statement(body_scope, Match(ast, Holding)->body),
"pthread_mutex_unlock(&mutexed->mutex);\n}");
}
case For: {
auto for_ = Match(ast, For);
@ -1811,7 +1775,7 @@ CORD expr_as_text(CORD expr, type_t *t, CORD color)
case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(t));
case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(t));
case OptionalType: return CORD_asprintf("Optional$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(t));
case StructType: case EnumType: case MutexedType:
case StructType: case EnumType:
return CORD_asprintf("generic_as_text(stack(%r), %r, %r)", expr, color, compile_type_info(t));
default: compiler_err(NULL, NULL, NULL, "Stringifying is not supported for ", type_to_str(t));
}
@ -2199,8 +2163,7 @@ CORD compile_none(type_t *t)
if (t->tag == OptionalType)
t = Match(t, OptionalType)->type;
if (t == THREAD_TYPE) return "NULL";
else if (t == PATH_TYPE) return "NONE_PATH";
if (t == PATH_TYPE) return "NONE_PATH";
else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})";
else if (t == MATCH_TYPE) return "NONE_MATCH";
@ -2231,7 +2194,6 @@ CORD compile_none(type_t *t)
env_t *enum_env = Match(t, EnumType)->env;
return CORD_all("((", compile_type(t), "){", namespace_prefix(enum_env, enum_env->namespace), "null})");
}
case MutexedType: return "NONE_MUTEXED_DATA";
default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_str(t));
}
}
@ -2339,36 +2301,6 @@ CORD compile(env_t *env, ast_t *ast)
else
return CORD_all("stack(", compile(env, subject), ")");
}
case Mutexed: {
ast_t *mutexed = Match(ast, Mutexed)->value;
return CORD_all("new(struct MutexedData_s, .mutex=PTHREAD_MUTEX_INITIALIZER, .data=", compile(env, WrapAST(mutexed, HeapAllocate, mutexed)), ")");
}
case Holding: {
ast_t *held = Match(ast, Holding)->mutexed;
type_t *held_type = get_type(env, held);
if (held_type->tag != MutexedType)
code_err(held, "This is a ", type_to_str(held_type), ", not a mutexed value");
CORD code = CORD_all(
"({ // Holding\n",
"MutexedData_t mutexed = ", compile(env, held), ";\n",
"pthread_mutex_lock(&mutexed->mutex);\n");
env_t *body_scope = fresh_scope(env);
body_scope->deferred = new(deferral_t, .defer_env=env,
.block=FakeAST(InlineCCode, .code="pthread_mutex_unlock(&mutexed->mutex);"),
.next=body_scope->deferred);
if (held->tag == Var) {
CORD held_var = CORD_all(Match(held, Var)->name, "$held");
set_binding(body_scope, Match(held, Var)->name,
Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true), held_var);
code = CORD_all(code, compile_declaration(Type(PointerType, .pointed=Match(held_type, MutexedType)->type), held_var),
" = (", compile_type(Type(PointerType, .pointed=Match(held_type, MutexedType)->type)), ")mutexed->data;\n");
}
type_t *body_type = get_type(env, ast);
return CORD_all(code, compile_declaration(body_type, "result"), " = ", compile(body_scope, Match(ast, Holding)->body), ";\n"
"pthread_mutex_unlock(&mutexed->mutex);\n"
"result;\n})");
}
case Optional: {
ast_t *value = Match(ast, Optional)->value;
CORD value_code = compile(env, value);
@ -3829,8 +3761,7 @@ CORD compile(env_t *env, ast_t *ast)
CORD compile_type_info(type_t *t)
{
if (t == THREAD_TYPE) return "&Thread$info";
else if (t == RNG_TYPE) return "&RNG$info";
if (t == RNG_TYPE) return "&RNG$info";
else if (t == MATCH_TYPE) return "&Match$info";
else if (t == PATH_TYPE) return "&Path$info";
else if (t == PATH_TYPE_TYPE) return "&PathType$info";
@ -3890,10 +3821,6 @@ CORD compile_type_info(type_t *t)
type_t *non_optional = Match(t, OptionalType)->type;
return CORD_asprintf("Optional$info(sizeof(%r), __alignof__(%r), %r)", compile_type(non_optional), compile_type(non_optional), compile_type_info(non_optional));
}
case MutexedType: {
type_t *mutexed = Match(t, MutexedType)->type;
return CORD_all("MutexedData$info(", compile_type_info(mutexed), ")");
}
case TypeInfoType: return CORD_all("Type$info(", CORD_quoted(type_to_cord(Match(t, TypeInfoType)->type)), ")");
case MemoryType: return "&Memory$info";
case VoidType: return "&Void$info";

View File

@ -16,7 +16,6 @@ type_t *TEXT_TYPE = NULL;
type_t *MATCH_TYPE = NULL;
type_t *RNG_TYPE = NULL;
public type_t *PATH_TYPE = NULL;
public type_t *THREAD_TYPE = NULL;
public type_t *PATH_TYPE_TYPE = NULL;
static type_t *declare_type(env_t *env, const char *def_str)
@ -70,7 +69,6 @@ env_t *global_env(void)
PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)");
MATCH_TYPE = declare_type(env, "struct Match(text:Text, index:Int, captures:[Text])");
PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])");
THREAD_TYPE = declare_type(env, "struct Thread(; opaque)");
RNG_TYPE = declare_type(env, "struct RNG(state:@Memory)");
typedef struct {
@ -394,12 +392,6 @@ env_t *global_env(void)
{"utf32_codepoints", "Text$utf32_codepoints", "func(text:Text -> [Int32])"},
{"width", "Text$width", "func(text:Text, language='C' -> Int)"},
)},
{"Thread", THREAD_TYPE, "Thread_t", "Thread", TypedArray(ns_entry_t,
{"new", "Thread$new", "func(fn:func() -> Thread)"},
{"cancel", "Thread$cancel", "func(thread:Thread)"},
{"join", "Thread$join", "func(thread:Thread)"},
{"detach", "Thread$detach", "func(thread:Thread)"},
)},
};
for (size_t i = 0; i < sizeof(global_types)/sizeof(global_types[0]); i++) {
@ -535,7 +527,6 @@ env_t *global_env(void)
{"Int$value_as_text", "func(i:Int -> Path)"});
ADD_CONSTRUCTORS("CString", {"Text$as_c_string", "func(text:Text -> CString)"});
ADD_CONSTRUCTORS("RNG", {"RNG$new", "func(-> RNG)"});
ADD_CONSTRUCTORS("Thread", {"Thread$new", "func(fn:func() -> Thread)"});
#undef ADD_CONSTRUCTORS
set_binding(namespace_env(env, "Path"), "from_text",

View File

@ -93,6 +93,5 @@ extern type_t *MATCH_TYPE;
extern type_t *RNG_TYPE;
extern type_t *PATH_TYPE;
extern type_t *PATH_TYPE_TYPE;
extern type_t *THREAD_TYPE;
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -63,8 +63,8 @@ int op_tightness[] = {
static const char *keywords[] = {
"yes", "xor", "while", "when", "use", "unless", "struct", "stop", "skip", "return",
"or", "not", "none", "no", "mutexed", "mod1", "mod", "pass", "lang", "inline", "in", "if",
"holding", "func", "for", "extern", "enum", "else", "do", "deserialize", "defer", "and",
"or", "not", "none", "no", "mod1", "mod", "pass", "lang", "inline", "in", "if",
"func", "for", "extern", "enum", "else", "do", "deserialize", "defer", "and",
"_min_", "_max_", NULL,
};
@ -94,7 +94,6 @@ static type_ast_t *parse_array_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_pointer_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_mutexed_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_set_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos);
@ -116,13 +115,11 @@ static PARSER(parse_file_body);
static PARSER(parse_for);
static PARSER(parse_func_def);
static PARSER(parse_heap_alloc);
static PARSER(parse_holding);
static PARSER(parse_if);
static PARSER(parse_inline_c);
static PARSER(parse_int);
static PARSER(parse_lambda);
static PARSER(parse_lang_def);
static PARSER(parse_mutexed);
static PARSER(parse_namespace);
static PARSER(parse_negative);
static PARSER(parse_not);
@ -573,19 +570,6 @@ type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) {
return ptr_type;
}
type_ast_t *parse_mutexed_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match_word(&pos, "mutexed")) return NULL;
spaces(&pos);
if (!match(&pos, "(")) return NULL;
spaces(&pos);
type_ast_t *mutexed = expect(ctx, start, &pos, parse_type,
"I couldn't parse a mutexed type after this point");
spaces(&pos);
if (!match(&pos, ")")) return NULL;
return NewTypeAST(ctx->file, start, pos, MutexedTypeAST, .type=mutexed);
}
type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
const char *id = get_id(&pos);
@ -612,7 +596,6 @@ type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) {
|| (type=parse_set_type(ctx, pos))
|| (type=parse_type_name(ctx, pos))
|| (type=parse_func_type(ctx, pos))
|| (type=parse_mutexed_type(ctx, pos))
);
if (!success && match(&pos, "(")) {
whitespace(&pos);
@ -1190,23 +1173,6 @@ PARSER(parse_stack_reference) {
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_holding) {
const char *start = pos;
if (!match_word(&pos, "holding")) return NULL;
spaces(&pos);
ast_t *mutexed = expect(ctx, start, &pos, parse_expr, "I expected an expression for this 'holding'");
ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a block to be here");
return NewAST(ctx->file, start, pos, Holding, .mutexed=mutexed, .body=body);
}
PARSER(parse_not) {
const char *start = pos;
if (!match_word(&pos, "not")) return NULL;
@ -1526,7 +1492,6 @@ PARSER(parse_term_no_suffix) {
|| (term=parse_stop(ctx, pos))
|| (term=parse_return(ctx, pos))
|| (term=parse_not(ctx, pos))
|| (term=parse_mutexed(ctx, pos))
|| (term=parse_extern(ctx, pos))
|| (term=parse_inline_c(ctx, pos))
);
@ -1858,7 +1823,6 @@ PARSER(parse_extended_expr) {
|| (expr=optional(ctx, &pos, parse_when))
|| (expr=optional(ctx, &pos, parse_repeat))
|| (expr=optional(ctx, &pos, parse_do))
|| (expr=optional(ctx, &pos, parse_holding))
)
return expr;

View File

@ -31,5 +31,4 @@ some common functionality.
- Pointers: [pointers.h](pointers.h), [pointers.c](pointers.c)
- Tables: [tables.h](tables.h), [tables.c](tables.c)
- Text: [text.h](text.h), [text.c](text.c)
- Threads: [threads.h](threads.h), [threads.c](threads.c)
- Type Infos (for representing types as values): [types.h](types.h), [types.c](types.c)

View File

@ -3,7 +3,6 @@
// Common datastructures (arrays, tables, closures)
#include <gmp.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
@ -111,11 +110,6 @@ typedef struct {
typedef struct RNGState_t* RNG_t;
typedef struct MutexedData_s {
pthread_mutex_t mutex;
void *data;
} *MutexedData_t;
#define OptionalBool_t uint8_t
#define OptionalArray_t Array_t
#define OptionalTable_t Table_t

View File

@ -1,39 +0,0 @@
// Mutexed data methods/type info
#include <ctype.h>
#include <err.h>
#include <gc.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/param.h>
#include "bools.h"
#include "metamethods.h"
#include "optionals.h"
#include "text.h"
#include "util.h"
static Text_t MutexedData$as_text(const void *m, bool colorize, const TypeInfo_t *type)
{
auto mutexed = type->MutexedDataInfo;
Text_t typename = generic_as_text(NULL, false, mutexed.type);
if (!m) {
return Texts(colorize ? Text("\x1b[34;1mmutexed\x1b[m(") : Text("mutexed("), typename, Text(")"));
}
return Texts(colorize ? Text("\x1b[34;1mmutexed ") : Text("mutexed "), typename,
Text$format(colorize ? "<%p>\x1b[m" : "<%p>", *((MutexedData_t*)m)));
}
static bool MutexedData$is_none(const void *m, const TypeInfo_t *)
{
return *((MutexedData_t*)m) == NULL;
}
public const metamethods_t MutexedData$metamethods = {
.as_text=MutexedData$as_text,
.is_none=MutexedData$is_none,
.serialize=cannot_serialize,
.deserialize=cannot_deserialize,
};
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -1,17 +0,0 @@
#pragma once
// Metamethods and type info for mutexed data
#include "types.h"
#include "optionals.h"
#include "util.h"
#define NONE_MUTEXED_DATA ((MutexedData_t)NULL)
extern const metamethods_t MutexedData$metamethods;
#define MutexedData$info(t) &((TypeInfo_t){.size=sizeof(MutexedData_t), .align=__alignof(MutexedData_t), \
.tag=MutexedDataInfo, .MutexedDataInfo.type=t, \
.metamethods=MutexedData$metamethods})
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -1,7 +1,5 @@
// Optional types
#include <pthread.h>
#include "bools.h"
#include "bytes.h"
#include "datatypes.h"
@ -10,7 +8,6 @@
#include "nums.h"
#include "patterns.h"
#include "text.h"
#include "threads.h"
#include "util.h"
public PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type)

View File

@ -1,92 +0,0 @@
// Logic for the Thread type, representing a pthread
#include <ctype.h>
#include <err.h>
#include <fcntl.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 "datatypes.h"
#include "metamethods.h"
#include "rng.h"
#include "text.h"
#include "threads.h"
#include "types.h"
#include "util.h"
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
(void)flags;
arc4random_buf(buf, buflen);
return buflen;
}
#elif defined(__linux__)
// Use getrandom()
# include <sys/random.h>
#else
#error "Unsupported platform for secure random number generation"
#endif
static void *run_thread(Closure_t *closure)
{
uint8_t *random_bytes = GC_MALLOC_ATOMIC(40);
getrandom(random_bytes, 40, 0);
Array_t rng_seed = {.length=40, .data=random_bytes, .stride=1, .atomic=1};
default_rng = RNG$new(rng_seed);
((void(*)(void*))closure->fn)(closure->userdata);
return NULL;
}
public Thread_t Thread$new(Closure_t fn)
{
Thread_t thread = GC_MALLOC(sizeof(pthread_t));
Closure_t *closure = new(Closure_t, .fn=fn.fn, .userdata=fn.userdata);
pthread_create(thread, NULL, (void*)run_thread, closure);
return thread;
}
public void Thread$join(Thread_t thread)
{
pthread_join(*thread, NULL);
}
public void Thread$cancel(Thread_t thread)
{
pthread_cancel(*thread);
}
public void Thread$detach(Thread_t thread)
{
pthread_detach(*thread);
}
Text_t Thread$as_text(const void *thread, bool colorize, const TypeInfo_t*)
{
if (!thread) {
return colorize ? Text("\x1b[34;1mThread\x1b[m") : Text("Thread");
}
return Text$format(colorize ? "\x1b[34;1mThread(%p)\x1b[m" : "Thread(%p)", *(Thread_t**)thread);
}
static bool Thread$is_none(const void *obj, const TypeInfo_t*)
{
return *(Thread_t*)obj == NULL;
}
public const TypeInfo_t Thread$info = {
.size=sizeof(Thread_t), .align=__alignof(Thread_t),
.metamethods={
.as_text=Thread$as_text,
.is_none=Thread$is_none,
.serialize=cannot_serialize,
.deserialize=cannot_deserialize,
},
};
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -1,22 +0,0 @@
#pragma once
// Logic for the Thread type, representing a pthread
#include <pthread.h>
#include <stdbool.h>
#include "datatypes.h"
#include "types.h"
#include "util.h"
#define Thread_t pthread_t*
Thread_t Thread$new(Closure_t fn);
void Thread$cancel(Thread_t thread);
void Thread$join(Thread_t thread);
void Thread$detach(Thread_t thread);
Text_t Thread$as_text(const void *thread, bool colorize, const TypeInfo_t *type);
extern const TypeInfo_t Thread$info;
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -17,7 +17,6 @@
#include "integers.h"
#include "memory.h"
#include "metamethods.h"
#include "mutexeddata.h"
#include "nums.h"
#include "optionals.h"
#include "paths.h"
@ -29,7 +28,6 @@
#include "structs.h"
#include "tables.h"
#include "text.h"
#include "threads.h"
#include "types.h"
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -30,7 +30,7 @@ struct TypeInfo_s {
metamethods_t metamethods;
struct { // Anonymous tagged union for convenience
enum { OpaqueInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, TableInfo, FunctionInfo,
OptionalInfo, MutexedDataInfo, TypeInfoInfo } tag;
OptionalInfo, TypeInfoInfo } tag;
union {
struct {} OpaqueInfo;
struct {
@ -54,7 +54,7 @@ struct TypeInfo_s {
} TypeInfoInfo;
struct {
const TypeInfo_t *type;
} OptionalInfo, MutexedDataInfo;
} OptionalInfo;
struct {
const char *name;
int num_tags;

View File

@ -114,7 +114,7 @@ int main(int argc, char *argv[])
#endif
#ifdef __OpenBSD__
ldlibs = Texts(ldlibs, Text(" -lexecinfo -lpthread"));
ldlibs = Texts(ldlibs, Text(" -lexecinfo"));
#endif
USE_COLOR = getenv("COLOR") ? strcmp(getenv("COLOR"), "1") == 0 : isatty(STDOUT_FILENO);

View File

@ -128,13 +128,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
code_err(ast, "Nested optional types are not currently supported");
return Type(OptionalType, .type=t);
}
case MutexedTypeAST: {
type_ast_t *mutexed = Match(ast, MutexedTypeAST)->type;
type_t *t = parse_type_ast(env, mutexed);
if (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType)
code_err(ast, "Mutexed ", type_to_str(t), " types are not supported.");
return Type(MutexedType, .type=t);
}
case UnknownTypeAST: code_err(ast, "I don't know how to get this type");
}
#ifdef __GNUC__
@ -1027,21 +1020,6 @@ 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 ", type_to_str(t));
}
case Mutexed: {
type_t *item_type = get_type(env, Match(ast, Mutexed)->value);
return Type(MutexedType, .type=item_type);
}
case Holding: {
ast_t *held = Match(ast, Holding)->mutexed;
type_t *held_type = get_type(env, held);
if (held_type->tag != MutexedType)
code_err(held, "This is a ", type_to_str(held_type), ", not a mutexed value");
if (held->tag == Var) {
env = fresh_scope(env);
set_binding(env, Match(held, Var)->name, Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true), CORD_EMPTY);
}
return get_type(env, Match(ast, Holding)->body);
}
case BinaryOp: {
auto binop = Match(ast, BinaryOp);
type_t *lhs_t = get_type(env, binop->lhs),

View File

@ -84,13 +84,6 @@ CORD type_to_cord(type_t *t) {
else
return "(Unknown optional type)";
}
case MutexedType: {
type_t *opt = Match(t, MutexedType)->type;
if (opt)
return CORD_all("mutexed ", type_to_cord(opt));
else
return "(Unknown optional type)";
}
case TypeInfoType: {
return CORD_all("Type$info(", Match(t, TypeInfoType)->name, ")");
}
@ -250,7 +243,6 @@ PUREFUNC bool has_heap_memory(type_t *t)
case SetType: return true;
case PointerType: return true;
case OptionalType: return has_heap_memory(Match(t, OptionalType)->type);
case MutexedType: return true;
case BigIntType: return true;
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
@ -276,7 +268,6 @@ PUREFUNC bool has_stack_memory(type_t *t)
switch (t->tag) {
case PointerType: return Match(t, PointerType)->is_stack;
case OptionalType: return has_stack_memory(Match(t, OptionalType)->type);
case MutexedType: return has_stack_memory(Match(t, MutexedType)->type);
default: return false;
}
}
@ -483,7 +474,6 @@ PUREFUNC size_t unpadded_struct_size(type_t *t)
PUREFUNC size_t type_size(type_t *t)
{
if (t == THREAD_TYPE) return sizeof(pthread_t*);
if (t == PATH_TYPE) return sizeof(Path_t);
if (t == PATH_TYPE_TYPE) return sizeof(PathType_t);
#ifdef __GNUC__
@ -514,7 +504,6 @@ PUREFUNC size_t type_size(type_t *t)
case FunctionType: return sizeof(void*);
case ClosureType: return sizeof(struct {void *fn, *userdata;});
case PointerType: return sizeof(void*);
case MutexedType: return sizeof(MutexedData_t);
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
@ -575,7 +564,6 @@ PUREFUNC size_t type_size(type_t *t)
PUREFUNC size_t type_align(type_t *t)
{
if (t == THREAD_TYPE) return __alignof__(pthread_t*);
if (t == PATH_TYPE) return __alignof__(Path_t);
if (t == PATH_TYPE_TYPE) return __alignof__(PathType_t);
#ifdef __GNUC__
@ -606,7 +594,6 @@ PUREFUNC size_t type_align(type_t *t)
case FunctionType: return __alignof__(void*);
case ClosureType: return __alignof__(struct {void *fn, *userdata;});
case PointerType: return __alignof__(void*);
case MutexedType: return __alignof__(MutexedData_t);
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {

View File

@ -57,7 +57,6 @@ struct type_s {
EnumType,
OptionalType,
TypeInfoType,
MutexedType,
ModuleType,
} tag;
@ -116,7 +115,7 @@ struct type_s {
} EnumType;
struct {
type_t *type;
} OptionalType, MutexedType;
} OptionalType;
struct {
const char *name;
type_t *type;

View File

@ -61,18 +61,6 @@ func maybe_c_string(should_i:Bool->CString?):
else:
return none
func maybe_thread(should_i:Bool->Thread?):
if should_i:
return Thread.new(func(): pass)
else:
return none
func maybe_mutexed(should_i:Bool->mutexed(Bool)?):
if should_i:
return mutexed no
else:
return none
func main():
>> 5?
= 5?
@ -244,33 +232,6 @@ func main():
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Threads:
>> yep := maybe_thread(yes)
# No "=" test here because threads use addresses in the text version
>> nope := maybe_thread(no)
= none : Thread
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Mutexed:
>> yep := maybe_mutexed(yes)
# No "=" test here because threads use addresses in the text version
>> nope := maybe_mutexed(no)
= none : mutexed(Bool)
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
if yep := maybe_int(yes):
>> yep
= 123

View File

@ -1,70 +0,0 @@
enum Job(Increment(x:Int), Decrement(x:Int))
func main():
do:
>> nums := mutexed [10, 20, 30]
holding nums:
>> nums[]
= [10, 20, 30]
nums:insert(40)
holding nums:
>> nums[]
= [10, 20, 30, 40]
jobs := mutexed [Job.Increment(5)]
results := mutexed [:Int]
>> thread := Thread.new(func():
!! In another thread!
repeat:
job := holding jobs: (jobs:pop(1) or stop)
when job is Increment(x):
holding results: results:insert(x + 1)
is Decrement(x):
holding results: results:insert(x - 1)
)
holding jobs:
jobs:insert(Decrement(100))
jobs:insert(Decrement(200))
jobs:insert(Decrement(300))
jobs:insert(Decrement(400))
jobs:insert(Decrement(500))
jobs:insert(Decrement(600))
jobs:insert(Increment(1000))
dequeue_result := func():
result := none:Int
repeat:
result = (holding results: results:pop(1))
stop if result
sleep(0.00001)
return result!
>> dequeue_result()
= 6
>> dequeue_result()
= 99
>> dequeue_result()
= 199
>> dequeue_result()
= 299
>> dequeue_result()
= 399
>> dequeue_result()
= 499
>> dequeue_result()
= 599
>> dequeue_result()
= 1001
!! Canceling...
>> thread:cancel()
!! Joining...
>> thread:join()
!! Done!