From 8ee23054bf771e56802ce21d70229b7f8f2e9654 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 15 Sep 2024 17:34:34 -0400 Subject: [PATCH] Update Inline C syntax and documentation/tests --- compile.c | 10 +++++-- docs/c-interoperability.md | 37 ++++++++++++++++++++++++++ examples/coroutine.tm | 20 +++++--------- examples/game/player.tm | 8 +++--- examples/http.tm | 4 +-- examples/log.tm | 8 +++--- parse.c | 53 +++++++++++++------------------------- test/inline_c.tm | 8 ++++++ 8 files changed, 86 insertions(+), 62 deletions(-) create mode 100644 docs/c-interoperability.md create mode 100644 test/inline_c.tm diff --git a/compile.c b/compile.c index 3aeebfd..61947d5 100644 --- a/compile.c +++ b/compile.c @@ -1354,7 +1354,13 @@ CORD compile_statement(env_t *env, ast_t *ast) return compile_statement(env, loop); } 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: { auto use = Match(ast, Use); if (use->what == USE_LOCAL) { @@ -3232,7 +3238,7 @@ CORD compile(env_t *env, ast_t *ast) if (t->tag == VoidType) return CORD_all("{\n", Match(ast, InlineCCode)->code, "\n}"); else - return Match(ast, InlineCCode)->code; + return CORD_all("({ ", Match(ast, InlineCCode)->code, "; })"); } case Use: code_err(ast, "Compiling 'use' as expression!"); case Defer: code_err(ast, "Compiling 'defer' as expression!"); diff --git a/docs/c-interoperability.md b/docs/c-interoperability.md new file mode 100644 index 0000000..68a8434 --- /dev/null +++ b/docs/c-interoperability.md @@ -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 ` 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. diff --git a/examples/coroutine.tm b/examples/coroutine.tm index 1d3d4dc..35eb5fd 100644 --- a/examples/coroutine.tm +++ b/examples/coroutine.tm @@ -29,16 +29,12 @@ _shared_stack := !@Memory struct Coroutine(co:@Memory): func is_finished(co:Coroutine; inline)->Bool: - return inline C ( - ((aco_t*)$co.$co)->is_finished - ):Bool + return inline C:Bool {((aco_t*)$co.$co)->is_finished} func resume(co:Coroutine)->Bool: if co:is_finished(): return no - inline C { - aco_resume($co.$co); - } + inline C { aco_resume($co.$co); } return yes func _init(): @@ -46,13 +42,9 @@ func _init(): aco_set_allocator(GC_malloc, NULL); aco_thread_init(aco_exit_fn); } - _main_co = inline C( - aco_create(NULL, NULL, 0, NULL, NULL) - ):@Memory + _main_co = inline C:@Memory { aco_create(NULL, NULL, 0, NULL, NULL) } - _shared_stack = inline C( - aco_shared_stack_new(0); - ):@Memory + _shared_stack = inline C:@Memory { aco_shared_stack_new(0) } func new(co:func())->Coroutine: if not _main_co: @@ -60,9 +52,9 @@ func new(co:func())->Coroutine: main_co := _main_co 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) - ):@Memory + } return Coroutine(aco_ptr) func yield(; inline): diff --git a/examples/game/player.tm b/examples/game/player.tm index dff95b9..5909ae6 100644 --- a/examples/game/player.tm +++ b/examples/game/player.tm @@ -13,12 +13,12 @@ struct Player(pos,prev_pos:Vec2): SIZE := Vec2(30, 30) 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 - target_y := inline C ( + } + target_y := inline C:Num { (Num_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0)) - ) : Num + } target_vel := Vec2(target_x, target_y):norm() * WALK_SPEED vel := (p.pos - p.prev_pos)/World.DT diff --git a/examples/http.tm b/examples/http.tm index 0a27ece..bbec330 100644 --- a/examples/http.tm +++ b/examples/http.tm @@ -12,9 +12,9 @@ _curl := !@Memory func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse: chunks := @[:Text] 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) + }) return n*size inline C { diff --git a/examples/log.tm b/examples/log.tm index 24397c9..42df072 100644 --- a/examples/log.tm +++ b/examples/log.tm @@ -6,15 +6,13 @@ timestamp_format := CString("%F %T") logfiles := {:Path} func _timestamp()->Text: - c_str := inline C ( - ({ + c_str := inline C:CString { char *str = GC_MALLOC_ATOMIC(20); time_t t; time(&t); struct tm *tm_info = localtime(&t); strftime(str, 20, "%F %T", tm_info); - str; - }) - ) : CString + str + } return c_str:as_text() func info(text:Text, newline=yes): diff --git a/parse.c b/parse.c index 34c021c..523c54f 100644 --- a/parse.c +++ b/parse.c @@ -366,22 +366,6 @@ size_t match_word(const char **out, const char *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 *word = *inout; spaces(&word); @@ -2258,31 +2242,30 @@ PARSER(parse_extern) { PARSER(parse_inline_c) { const char *start = pos; if (!match_word(&pos, "inline")) return NULL; + spaces(&pos); 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); type_ast_t *type = NULL; - if (open == '(') { - if (!match(&pos, ":")) - 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"); + if (match(&pos, ":")) + type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this inline C code"); + + 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); } diff --git a/test/inline_c.tm b/test/inline_c.tm new file mode 100644 index 0000000..876e78f --- /dev/null +++ b/test/inline_c.tm @@ -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); + }