aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compile.c10
-rw-r--r--docs/c-interoperability.md37
-rw-r--r--examples/coroutine.tm20
-rw-r--r--examples/game/player.tm8
-rw-r--r--examples/http.tm4
-rw-r--r--examples/log.tm8
-rw-r--r--parse.c53
-rw-r--r--test/inline_c.tm8
8 files changed, 86 insertions, 62 deletions
diff --git a/compile.c b/compile.c
index 3aeebfd6..61947d51 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 00000000..68a84342
--- /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 <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.
diff --git a/examples/coroutine.tm b/examples/coroutine.tm
index 1d3d4dcf..35eb5fda 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 dff95b9d..5909ae63 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 0a27ece2..bbec3308 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 24397c90..42df072c 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 34c021c6..523c54f7 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 00000000..876e78fd
--- /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);
+ }