Update Inline C syntax and documentation/tests

This commit is contained in:
Bruce Hill 2024-09-15 17:34:34 -04:00
parent 3cbc62ee43
commit 8ee23054bf
8 changed files with 86 additions and 62 deletions

View File

@ -1354,7 +1354,13 @@ CORD compile_statement(env_t *env, ast_t *ast)
return compile_statement(env, loop); return compile_statement(env, loop);
} }
case Extern: return CORD_EMPTY; case Extern: return CORD_EMPTY;
case InlineCCode: return Match(ast, InlineCCode)->code; case InlineCCode: {
auto inline_code = Match(ast, InlineCCode);
if (inline_code->type)
return CORD_all("({ ", inline_code->code, "; })");
else
return inline_code->code;
}
case Use: { case Use: {
auto use = Match(ast, Use); auto use = Match(ast, Use);
if (use->what == USE_LOCAL) { if (use->what == USE_LOCAL) {
@ -3232,7 +3238,7 @@ CORD compile(env_t *env, ast_t *ast)
if (t->tag == VoidType) if (t->tag == VoidType)
return CORD_all("{\n", Match(ast, InlineCCode)->code, "\n}"); return CORD_all("{\n", Match(ast, InlineCCode)->code, "\n}");
else else
return Match(ast, InlineCCode)->code; return CORD_all("({ ", Match(ast, InlineCCode)->code, "; })");
} }
case Use: code_err(ast, "Compiling 'use' as expression!"); case Use: code_err(ast, "Compiling 'use' as expression!");
case Defer: code_err(ast, "Compiling 'defer' as expression!"); case Defer: code_err(ast, "Compiling 'defer' as expression!");

View File

@ -0,0 +1,37 @@
# C Interoperability
Tomo is intended to be used as a complete, standalone programming language, but
it's also meant to be easy to integrate with existing C libraries. In order to
make this possible, there are a few tools available.
## Using C Libraries
In order to link against a compiled shared library, you can use `use libfoo.so`
to cause Tomo to add `-l:libfoo.so` to the linker flags when compiling your
final executable. You can also use `use <foo.h>` or `use "foo.h"` to cause Tomo
to insert a corresponding `#include` when compiling your code.
## Inline C Code
As a final escape hatch, you can use `inline C` to add code that will be put,
verbatim in the transpiled C code generated by Tomo. There are two forms: one
that creates an expression value and one that creates a block that is executed
without evaluating to anything:
```tomo
# Inline C block:
inline C {
printf("This is just a block that is executed without a return value\n");
}
# Inline C expression (you must specify a type)
val := inline C : Int32 {
int x = 1; x + 1
}
```
Inline C expressions must specify a type and they can be [compound statement
expressions](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html). In
other words, if an inline C expression has a type, it will be enclosed
with `({ ...; })` so that you can put semicolon-terminated statements before
the final expression in their own scope if you want.

View File

@ -29,16 +29,12 @@ _shared_stack := !@Memory
struct Coroutine(co:@Memory): struct Coroutine(co:@Memory):
func is_finished(co:Coroutine; inline)->Bool: func is_finished(co:Coroutine; inline)->Bool:
return inline C ( return inline C:Bool {((aco_t*)$co.$co)->is_finished}
((aco_t*)$co.$co)->is_finished
):Bool
func resume(co:Coroutine)->Bool: func resume(co:Coroutine)->Bool:
if co:is_finished(): if co:is_finished():
return no return no
inline C { inline C { aco_resume($co.$co); }
aco_resume($co.$co);
}
return yes return yes
func _init(): func _init():
@ -46,13 +42,9 @@ func _init():
aco_set_allocator(GC_malloc, NULL); aco_set_allocator(GC_malloc, NULL);
aco_thread_init(aco_exit_fn); aco_thread_init(aco_exit_fn);
} }
_main_co = inline C( _main_co = inline C:@Memory { aco_create(NULL, NULL, 0, NULL, NULL) }
aco_create(NULL, NULL, 0, NULL, NULL)
):@Memory
_shared_stack = inline C( _shared_stack = inline C:@Memory { aco_shared_stack_new(0) }
aco_shared_stack_new(0);
):@Memory
func new(co:func())->Coroutine: func new(co:func())->Coroutine:
if not _main_co: if not _main_co:
@ -60,9 +52,9 @@ func new(co:func())->Coroutine:
main_co := _main_co main_co := _main_co
shared_stack := _shared_stack shared_stack := _shared_stack
aco_ptr := inline C ( aco_ptr := inline C:@Memory {
aco_create($main_co, $shared_stack, 0, (void*)$co.fn, $co.userdata) aco_create($main_co, $shared_stack, 0, (void*)$co.fn, $co.userdata)
):@Memory }
return Coroutine(aco_ptr) return Coroutine(aco_ptr)
func yield(; inline): func yield(; inline):

View File

@ -13,12 +13,12 @@ struct Player(pos,prev_pos:Vec2):
SIZE := Vec2(30, 30) SIZE := Vec2(30, 30)
func update(p:&Player): func update(p:&Player):
target_x := inline C ( target_x := inline C:Num {
(Num_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0)) (Num_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0))
) : Num }
target_y := inline C ( target_y := inline C:Num {
(Num_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0)) (Num_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0))
) : Num }
target_vel := Vec2(target_x, target_y):norm() * WALK_SPEED target_vel := Vec2(target_x, target_y):norm() * WALK_SPEED
vel := (p.pos - p.prev_pos)/World.DT vel := (p.pos - p.prev_pos)/World.DT

View File

@ -12,9 +12,9 @@ _curl := !@Memory
func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse: func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse:
chunks := @[:Text] chunks := @[:Text]
save_chunk := func(chunk:CString, size:Int64, n:Int64): save_chunk := func(chunk:CString, size:Int64, n:Int64):
chunks:insert(inline C ( chunks:insert(inline C:Text {
Text$format("%.*s", $size*$n, $chunk) Text$format("%.*s", $size*$n, $chunk)
) : Text) })
return n*size return n*size
inline C { inline C {

View File

@ -6,15 +6,13 @@ timestamp_format := CString("%F %T")
logfiles := {:Path} logfiles := {:Path}
func _timestamp()->Text: func _timestamp()->Text:
c_str := inline C ( c_str := inline C:CString {
({
char *str = GC_MALLOC_ATOMIC(20); char *str = GC_MALLOC_ATOMIC(20);
time_t t; time(&t); time_t t; time(&t);
struct tm *tm_info = localtime(&t); struct tm *tm_info = localtime(&t);
strftime(str, 20, "%F %T", tm_info); strftime(str, 20, "%F %T", tm_info);
str; str
}) }
) : CString
return c_str:as_text() return c_str:as_text()
func info(text:Text, newline=yes): func info(text:Text, newline=yes):

53
parse.c
View File

@ -366,22 +366,6 @@ size_t match_word(const char **out, const char *word) {
return strlen(word); return strlen(word);
} }
bool match_group(const char **out, char open) {
static char mirror_delim[256] = {['(']=')', ['{']='}', ['<']='>', ['[']=']'};
const char *pos = *out;
if (*pos != open) return 0;
char close = mirror_delim[(int)open] ? mirror_delim[(int)open] : open;
int depth = 1;
for (++pos; *pos && depth > 0; ++pos) {
if (*pos == close) --depth;
else if (*pos == open) ++depth;
}
if (depth == 0) {
*out = pos;
return true;
} else return false;
}
const char *get_word(const char **inout) { const char *get_word(const char **inout) {
const char *word = *inout; const char *word = *inout;
spaces(&word); spaces(&word);
@ -2258,31 +2242,30 @@ PARSER(parse_extern) {
PARSER(parse_inline_c) { PARSER(parse_inline_c) {
const char *start = pos; const char *start = pos;
if (!match_word(&pos, "inline")) return NULL; if (!match_word(&pos, "inline")) return NULL;
spaces(&pos); spaces(&pos);
if (!match_word(&pos, "C")) return NULL; if (!match_word(&pos, "C")) return NULL;
spaces(&pos);
char open = *pos;
if (!match(&pos, "(") && !match(&pos, "{"))
parser_err(ctx, start, pos, "I expected a '(' or '{' here");
int64_t indent = get_indent(ctx, pos);
whitespace(&pos);
// Block:
CORD c_code = CORD_EMPTY;
while (get_indent(ctx, pos) > indent) {
size_t line_len = strcspn(pos, "\r\n");
c_code = CORD_all(c_code, GC_strndup(pos, line_len), "\n");
pos += line_len;
if (whitespace(&pos) == WHITESPACE_NONE) break;
}
expect_closing(ctx, &pos, open == '(' ? ")" : "}", "I wasn't able to parse the rest of this inline C");
spaces(&pos); spaces(&pos);
type_ast_t *type = NULL; type_ast_t *type = NULL;
if (open == '(') { if (match(&pos, ":"))
if (!match(&pos, ":")) type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this inline C code");
parser_err(ctx, start, pos, "This inline C needs to have a type after it");
type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this extern"); spaces(&pos);
if (!match(&pos, "{"))
parser_err(ctx, start, pos, "I expected a '{' here");
int depth = 1;
const char *c_code_start = pos;
for (; *pos && depth > 0; ++pos) {
if (*pos == '}') --depth;
else if (*pos == '{') ++depth;
} }
if (depth != 0)
parser_err(ctx, start, start+1, "I couldn't find the closing '}' for this inline C code");
CORD c_code = GC_strndup(c_code_start, (size_t)((pos-1) - c_code_start));
return NewAST(ctx->file, start, pos, InlineCCode, .code=c_code, .type_ast=type); return NewAST(ctx->file, start, pos, InlineCCode, .code=c_code, .type_ast=type);
} }

8
test/inline_c.tm Normal file
View File

@ -0,0 +1,8 @@
func main():
>> inline C:Int32 { int x = 1 + 2; x }
= 3[32]
>> inline C {
say(Text("Inline C code works!"), true);
}