Update Inline C syntax and documentation/tests
This commit is contained in:
parent
3cbc62ee43
commit
8ee23054bf
10
compile.c
10
compile.c
@ -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!");
|
||||||
|
37
docs/c-interoperability.md
Normal file
37
docs/c-interoperability.md
Normal 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.
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
53
parse.c
@ -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
8
test/inline_c.tm
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user