mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
Structs, arrays, owned strings, memory model, modules, debug info
- struct with default values; constructors Point(1, 2) / Point(x: 1); field access, auto-deref through pointers - []T arrays with bounds checks, literals, for-each, push/pop/insert/remove/clear/resize/find/contains/join/slice/reverse/sort - str carries its length: .len, s[i], +, comparison, sub/find/split/trim/upper/lower/replace/repeat/bytes, str(x), int(s), float(s) - Memory: values own their memory and are freed at the end of their block; assignment copies, temporaries move; parameters are lent and only copied when the function changes them; defer; new()/free() for manual heap memory - input(), read_file/write_file/file_exists, exit(), fn main([]str args) - Finch modules: import name, module.fn(), module.Struct - C structs from headers (fields, fixed arrays, nested); passed and returned by value with the System V x86-64 ABI; addr(fn) gives C a callback - link "file.c" compiles and links your own C code - -g emits DWARF debug info (gdb/lldb) - Small C runtime (runtime/finch_rt.c) embedded in the compiler, cached in ~/.cache/finch - Tests: 58, all clean under valgrind (MEMCHECK=1 tests/run.sh) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0591cd20f3
commit
1dc976bd8f
59 changed files with 4577 additions and 1047 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(finch VERSION 1.0.0 LANGUAGES CXX)
|
project(finch VERSION 2.0.0 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -13,9 +13,19 @@ add_executable(finch
|
||||||
src/lexer.cpp
|
src/lexer.cpp
|
||||||
src/parser.cpp
|
src/parser.cpp
|
||||||
src/codegen.cpp
|
src/codegen.cpp
|
||||||
|
src/codegen_types.cpp
|
||||||
|
src/codegen_builtins.cpp
|
||||||
|
src/abi.cpp
|
||||||
src/cimport.cpp
|
src/cimport.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The runtime (runtime/finch_rt.c) is embedded into the compiler as a string;
|
||||||
|
# finch compiles it once into ~/.cache/finch and links it into every program.
|
||||||
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/runtime/finch_rt.c)
|
||||||
|
file(READ ${CMAKE_SOURCE_DIR}/runtime/finch_rt.c FINCH_RT_SOURCE)
|
||||||
|
file(WRITE ${CMAKE_BINARY_DIR}/rt_source.inc "static const char *kRuntimeSource = R\"FINCHRT(${FINCH_RT_SOURCE})FINCHRT\";\n")
|
||||||
|
target_include_directories(finch PRIVATE ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
# libclang reads C headers for `import "stdio.h"`
|
# libclang reads C headers for `import "stdio.h"`
|
||||||
find_path(LIBCLANG_INCLUDE clang-c/Index.h HINTS ${LLVM_INCLUDE_DIRS})
|
find_path(LIBCLANG_INCLUDE clang-c/Index.h HINTS ${LLVM_INCLUDE_DIRS})
|
||||||
find_library(LIBCLANG NAMES clang libclang HINTS ${LLVM_LIBRARY_DIRS})
|
find_library(LIBCLANG NAMES clang libclang HINTS ${LLVM_LIBRARY_DIRS})
|
||||||
|
|
|
||||||
27
examples/guess.fn
Normal file
27
examples/guess.fn
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Guess the number: input, loops and conversions.
|
||||||
|
import "stdlib.h"
|
||||||
|
import "time.h"
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
srand(u32(time(null)))
|
||||||
|
secret := rand() % 100 + 1
|
||||||
|
tries := 0
|
||||||
|
print("I'm thinking of a number from 1 to 100.")
|
||||||
|
while true {
|
||||||
|
answer := input("Your guess: ")
|
||||||
|
if answer.len == 0 {
|
||||||
|
print("Bye!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guess := int(answer)
|
||||||
|
tries += 1
|
||||||
|
if guess < secret {
|
||||||
|
print("Higher!")
|
||||||
|
} else if guess > secret {
|
||||||
|
print("Lower!")
|
||||||
|
} else {
|
||||||
|
print("Yes! It was", secret, "- you needed", tries, "tries.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
examples/structs.fn
Normal file
49
examples/structs.fn
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Structs, arrays of structs, and a linked list on the heap.
|
||||||
|
import "math.h"
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
float x
|
||||||
|
float y
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shape {
|
||||||
|
str name
|
||||||
|
[]Point corners
|
||||||
|
int color = 0xFF8800
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int value
|
||||||
|
ptr[Node] next
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perimeter(Shape s) -> float {
|
||||||
|
total := 0.0
|
||||||
|
for i in 0..s.corners.len {
|
||||||
|
a := s.corners[i]
|
||||||
|
b := s.corners[(i + 1) % s.corners.len]
|
||||||
|
dx := b.x - a.x
|
||||||
|
dy := b.y - a.y
|
||||||
|
total += sqrt(dx * dx + dy * dy)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
square := Shape(name: "square", corners: [Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)])
|
||||||
|
print(square.name, square.corners.len, "corners, perimeter", perimeter(square))
|
||||||
|
print(square)
|
||||||
|
|
||||||
|
// a linked list: new(...) puts a value on the heap, free(...) gives it back
|
||||||
|
ptr[Node] head = null
|
||||||
|
for i in 0..3 {
|
||||||
|
head = new(Node(value: i, next: head))
|
||||||
|
}
|
||||||
|
n := head
|
||||||
|
while n != null {
|
||||||
|
print("node", n.value)
|
||||||
|
next := n.next
|
||||||
|
free(n)
|
||||||
|
n = next
|
||||||
|
}
|
||||||
|
}
|
||||||
53
examples/todo.fn
Normal file
53
examples/todo.fn
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// A tiny to-do list: structs, arrays, strings and files.
|
||||||
|
|
||||||
|
struct Task {
|
||||||
|
str text
|
||||||
|
bool done = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show([]Task tasks) {
|
||||||
|
if tasks.len == 0 {
|
||||||
|
print("(nothing to do)")
|
||||||
|
}
|
||||||
|
for i in 0..tasks.len {
|
||||||
|
mark := " "
|
||||||
|
if tasks[i].done {
|
||||||
|
mark = "x"
|
||||||
|
}
|
||||||
|
print("[" + mark + "]", i + 1, tasks[i].text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save([]Task tasks, str path) {
|
||||||
|
[]str lines
|
||||||
|
for t in tasks {
|
||||||
|
lines.push(str(t.done) + "|" + t.text)
|
||||||
|
}
|
||||||
|
write_file(path, lines.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(str path) -> []Task {
|
||||||
|
[]Task tasks
|
||||||
|
if !file_exists(path) {
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
for line in read_file(path).split("\n") {
|
||||||
|
if line.len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := line.split("|")
|
||||||
|
tasks.push(Task(text: parts[1], done: parts[0] == "true"))
|
||||||
|
}
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
path := "todo.txt"
|
||||||
|
tasks := load(path)
|
||||||
|
tasks.push(Task(text: "learn Finch"))
|
||||||
|
tasks.push(Task(text: "write a game"))
|
||||||
|
tasks[0].done = true
|
||||||
|
show(tasks)
|
||||||
|
save(tasks, path)
|
||||||
|
print("saved", tasks.len, "tasks to", path)
|
||||||
|
}
|
||||||
426
runtime/finch_rt.c
Normal file
426
runtime/finch_rt.c
Normal file
|
|
@ -0,0 +1,426 @@
|
||||||
|
// The Finch runtime: string and array helpers, input, files and runtime errors.
|
||||||
|
// It is embedded in the compiler, compiled once into a cache folder, and linked
|
||||||
|
// statically into every Finch program. Everything here is plain C with no state.
|
||||||
|
//
|
||||||
|
// Layout shared with the compiler (src/codegen.cpp):
|
||||||
|
// str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated
|
||||||
|
// cap > 0 : heap memory owned by the value (cap bytes allocated)
|
||||||
|
// cap == 0 : a string literal, valid forever, never written to
|
||||||
|
// cap == -1: borrowed from C, must be copied before it is kept
|
||||||
|
// []T = { T *ptr; int64 len; int64 cap } owns ptr when cap > 0
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *ptr;
|
||||||
|
int64_t len;
|
||||||
|
int64_t cap;
|
||||||
|
} FStr;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *ptr;
|
||||||
|
int64_t len;
|
||||||
|
int64_t cap;
|
||||||
|
} FArr;
|
||||||
|
|
||||||
|
static char empty[1] = "";
|
||||||
|
|
||||||
|
// ---------- runtime errors ----------
|
||||||
|
|
||||||
|
__attribute__((noreturn, cold)) void finch_panic(const char *file, int64_t line, const char *msg) {
|
||||||
|
fflush(stdout);
|
||||||
|
fprintf(stderr, "%s:%lld: runtime error: %s\n", file, (long long)line, msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((noreturn, cold)) void finch_panic_index(const char *file, int64_t line, int64_t i, int64_t len) {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof msg, "index %lld is out of range (the length is %lld)", (long long)i, (long long)len);
|
||||||
|
finch_panic(file, line, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *must(void *p) {
|
||||||
|
if (!p) {
|
||||||
|
fflush(stdout);
|
||||||
|
fprintf(stderr, "runtime error: out of memory\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *finch_alloc(int64_t n) { return must(malloc(n ? (size_t)n : 1)); }
|
||||||
|
void *finch_zalloc(int64_t n) { return must(calloc(1, n ? (size_t)n : 1)); }
|
||||||
|
|
||||||
|
// ---------- strings ----------
|
||||||
|
|
||||||
|
static void str_heap(FStr *out, const char *p, int64_t len) {
|
||||||
|
char *m = finch_alloc(len + 1);
|
||||||
|
if (len) memcpy(m, p, (size_t)len);
|
||||||
|
m[len] = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void str_static(FStr *out, const char *lit) {
|
||||||
|
out->ptr = (char *)lit;
|
||||||
|
out->len = (int64_t)strlen(lit);
|
||||||
|
out->cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copy that can be kept: literals are shared, everything else is duplicated.
|
||||||
|
void finch_str_copy(FStr *out, const FStr *s) {
|
||||||
|
if (s->cap == 0) *out = *s;
|
||||||
|
else str_heap(out, s->ptr, s->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make `s` writable (before s[i] = c).
|
||||||
|
void finch_str_own(FStr *s) {
|
||||||
|
if (s->cap > 0) return;
|
||||||
|
FStr t;
|
||||||
|
str_heap(&t, s->ptr, s->len);
|
||||||
|
*s = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_drop(FStr *s) {
|
||||||
|
if (s->cap > 0) free(s->ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_c(FStr *out, const char *p) {
|
||||||
|
if (!p) {
|
||||||
|
str_static(out, empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out->ptr = (char *)p;
|
||||||
|
out->len = (int64_t)strlen(p);
|
||||||
|
out->cap = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_concat(FStr *out, const FStr *a, const FStr *b) {
|
||||||
|
int64_t len = a->len + b->len;
|
||||||
|
char *m = finch_alloc(len + 1);
|
||||||
|
memcpy(m, a->ptr, (size_t)a->len);
|
||||||
|
memcpy(m + a->len, b->ptr, (size_t)b->len);
|
||||||
|
m[len] = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_str_eq(const FStr *a, const FStr *b) {
|
||||||
|
return a->len == b->len && memcmp(a->ptr, b->ptr, (size_t)a->len) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t finch_str_cmp(const FStr *a, const FStr *b) {
|
||||||
|
int64_t n = a->len < b->len ? a->len : b->len;
|
||||||
|
int c = memcmp(a->ptr, b->ptr, (size_t)n);
|
||||||
|
if (c) return c < 0 ? -1 : 1;
|
||||||
|
return a->len < b->len ? -1 : a->len > b->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_int(FStr *out, int64_t v) {
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
|
||||||
|
str_heap(out, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_uint(FStr *out, uint64_t v) {
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof buf, "%llu", (unsigned long long)v);
|
||||||
|
str_heap(out, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_float(FStr *out, double v) {
|
||||||
|
char buf[64];
|
||||||
|
int n = snprintf(buf, sizeof buf, "%g", v);
|
||||||
|
str_heap(out, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_char(FStr *out, char c) { str_heap(out, &c, 1); }
|
||||||
|
|
||||||
|
void finch_str_from_bool(FStr *out, int b) { str_static(out, b ? "true" : "false"); }
|
||||||
|
|
||||||
|
static void bad_number(const FStr *s, const char *kind, const char *file, int64_t line) {
|
||||||
|
char msg[200];
|
||||||
|
snprintf(msg, sizeof msg, "can't turn \"%.*s\" into %s", (int)(s->len > 100 ? 100 : s->len), s->ptr, kind);
|
||||||
|
finch_panic(file, line, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t finch_str_to_int(const FStr *s, const char *file, int64_t line) {
|
||||||
|
const char *p = s->ptr;
|
||||||
|
while (isspace((unsigned char)*p)) p++;
|
||||||
|
char *end;
|
||||||
|
errno = 0;
|
||||||
|
long long v = strtoll(p, &end, 10);
|
||||||
|
while (isspace((unsigned char)*end)) end++;
|
||||||
|
if (end == p || *end || errno == ERANGE) bad_number(s, "int", file, line);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
double finch_str_to_float(const FStr *s, const char *file, int64_t line) {
|
||||||
|
const char *p = s->ptr;
|
||||||
|
while (isspace((unsigned char)*p)) p++;
|
||||||
|
char *end;
|
||||||
|
double v = strtod(p, &end);
|
||||||
|
while (isspace((unsigned char)*end)) end++;
|
||||||
|
if (end == p || *end) bad_number(s, "float", file, line);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_sub(FStr *out, const FStr *s, int64_t start, int64_t end, const char *file, int64_t line) {
|
||||||
|
if (start < 0 || end > s->len || start > end) {
|
||||||
|
char msg[160];
|
||||||
|
snprintf(msg, sizeof msg, "sub(%lld, %lld) is out of range (the length is %lld)", (long long)start,
|
||||||
|
(long long)end, (long long)s->len);
|
||||||
|
finch_panic(file, line, msg);
|
||||||
|
}
|
||||||
|
str_heap(out, s->ptr + start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t finch_str_find(const FStr *s, const FStr *t) {
|
||||||
|
if (t->len == 0) return 0;
|
||||||
|
for (int64_t i = 0; i + t->len <= s->len; i++)
|
||||||
|
if (s->ptr[i] == t->ptr[0] && memcmp(s->ptr + i, t->ptr, (size_t)t->len) == 0) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_str_starts(const FStr *s, const FStr *t) {
|
||||||
|
return t->len <= s->len && memcmp(s->ptr, t->ptr, (size_t)t->len) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_str_ends(const FStr *s, const FStr *t) {
|
||||||
|
return t->len <= s->len && memcmp(s->ptr + s->len - t->len, t->ptr, (size_t)t->len) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_trim(FStr *out, const FStr *s) {
|
||||||
|
int64_t a = 0, b = s->len;
|
||||||
|
while (a < b && isspace((unsigned char)s->ptr[a])) a++;
|
||||||
|
while (b > a && isspace((unsigned char)s->ptr[b - 1])) b--;
|
||||||
|
str_heap(out, s->ptr + a, b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_upper(FStr *out, const FStr *s) {
|
||||||
|
str_heap(out, s->ptr, s->len);
|
||||||
|
for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)toupper((unsigned char)out->ptr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_lower(FStr *out, const FStr *s) {
|
||||||
|
str_heap(out, s->ptr, s->len);
|
||||||
|
for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)tolower((unsigned char)out->ptr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_replace(FStr *out, const FStr *s, const FStr *from, const FStr *to) {
|
||||||
|
if (from->len == 0) {
|
||||||
|
str_heap(out, s->ptr, s->len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int64_t count = 0;
|
||||||
|
for (int64_t i = 0; i + from->len <= s->len;) {
|
||||||
|
if (memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {
|
||||||
|
count++;
|
||||||
|
i += from->len;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int64_t len = s->len + count * (to->len - from->len);
|
||||||
|
char *m = finch_alloc(len + 1), *w = m;
|
||||||
|
for (int64_t i = 0; i < s->len;) {
|
||||||
|
if (i + from->len <= s->len && memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {
|
||||||
|
memcpy(w, to->ptr, (size_t)to->len);
|
||||||
|
w += to->len;
|
||||||
|
i += from->len;
|
||||||
|
} else {
|
||||||
|
*w++ = s->ptr[i++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*w = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arr_push_str(FArr *a, const char *p, int64_t len);
|
||||||
|
|
||||||
|
// "a,b,,c".split(",") -> ["a", "b", "", "c"]; split("") gives single characters.
|
||||||
|
void finch_str_split(FArr *out, const FStr *s, const FStr *sep) {
|
||||||
|
out->ptr = NULL;
|
||||||
|
out->len = out->cap = 0;
|
||||||
|
if (sep->len == 0) {
|
||||||
|
for (int64_t i = 0; i < s->len; i++) arr_push_str(out, s->ptr + i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int64_t start = 0;
|
||||||
|
for (int64_t i = 0; i + sep->len <= s->len;) {
|
||||||
|
if (memcmp(s->ptr + i, sep->ptr, (size_t)sep->len) == 0) {
|
||||||
|
arr_push_str(out, s->ptr + start, i - start);
|
||||||
|
i += sep->len;
|
||||||
|
start = i;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr_push_str(out, s->ptr + start, s->len - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_join(FStr *out, const FArr *parts, const FStr *sep) {
|
||||||
|
const FStr *p = parts->ptr;
|
||||||
|
int64_t len = 0;
|
||||||
|
for (int64_t i = 0; i < parts->len; i++) len += p[i].len + (i ? sep->len : 0);
|
||||||
|
char *m = finch_alloc(len + 1), *w = m;
|
||||||
|
for (int64_t i = 0; i < parts->len; i++) {
|
||||||
|
if (i) {
|
||||||
|
memcpy(w, sep->ptr, (size_t)sep->len);
|
||||||
|
w += sep->len;
|
||||||
|
}
|
||||||
|
memcpy(w, p[i].ptr, (size_t)p[i].len);
|
||||||
|
w += p[i].len;
|
||||||
|
}
|
||||||
|
*w = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_repeat(FStr *out, const FStr *s, int64_t n) {
|
||||||
|
if (n < 0) n = 0;
|
||||||
|
int64_t len = s->len * n;
|
||||||
|
char *m = finch_alloc(len + 1);
|
||||||
|
for (int64_t i = 0; i < n; i++) memcpy(m + i * s->len, s->ptr, (size_t)s->len);
|
||||||
|
m[len] = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_str_from_bytes(FStr *out, const FArr *bytes) { str_heap(out, bytes->ptr, bytes->len); }
|
||||||
|
|
||||||
|
// ---------- arrays ----------
|
||||||
|
|
||||||
|
void finch_arr_reserve(FArr *a, int64_t need, int64_t es) {
|
||||||
|
if (need <= a->cap) return;
|
||||||
|
int64_t cap = a->cap * 2;
|
||||||
|
if (cap < 4) cap = 4;
|
||||||
|
if (cap < need) cap = need;
|
||||||
|
a->ptr = must(realloc(a->cap > 0 ? a->ptr : NULL, (size_t)(cap * es)));
|
||||||
|
a->cap = cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_arr_make(FArr *out, int64_t len, int64_t es) {
|
||||||
|
if (len < 0) len = 0;
|
||||||
|
out->ptr = len ? finch_zalloc(len * es) : NULL;
|
||||||
|
out->len = len;
|
||||||
|
out->cap = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow or shrink to `len`; new elements are zero
|
||||||
|
void finch_arr_resize(FArr *a, int64_t len, int64_t es) {
|
||||||
|
if (len < 0) len = 0;
|
||||||
|
finch_arr_reserve(a, len, es);
|
||||||
|
if (len > a->len) memset((char *)a->ptr + a->len * es, 0, (size_t)((len - a->len) * es));
|
||||||
|
a->len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a byte-for-byte copy of the elements (the compiler deep-copies owning elements afterwards)
|
||||||
|
void finch_arr_clone_raw(FArr *out, const FArr *a, int64_t es) {
|
||||||
|
out->len = a->len;
|
||||||
|
out->cap = a->len;
|
||||||
|
out->ptr = a->len ? finch_alloc(a->len * es) : NULL;
|
||||||
|
if (a->len) memcpy(out->ptr, a->ptr, (size_t)(a->len * es));
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_arr_free(FArr *a) {
|
||||||
|
if (a->cap > 0) free(a->ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a hole at i (0 <= i <= len), len grows by one
|
||||||
|
void finch_arr_insert_gap(FArr *a, int64_t i, int64_t es, const char *file, int64_t line) {
|
||||||
|
if (i < 0 || i > a->len) finch_panic_index(file, line, i, a->len);
|
||||||
|
finch_arr_reserve(a, a->len + 1, es);
|
||||||
|
char *p = a->ptr;
|
||||||
|
memmove(p + (i + 1) * es, p + i * es, (size_t)((a->len - i) * es));
|
||||||
|
a->len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the hole at i (the element was already moved out), len shrinks by one
|
||||||
|
void finch_arr_remove_gap(FArr *a, int64_t i, int64_t es) {
|
||||||
|
char *p = a->ptr;
|
||||||
|
memmove(p + i * es, p + (i + 1) * es, (size_t)((a->len - i - 1) * es));
|
||||||
|
a->len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arr_push_str(FArr *a, const char *p, int64_t len) {
|
||||||
|
finch_arr_reserve(a, a->len + 1, (int64_t)sizeof(FStr));
|
||||||
|
str_heap((FStr *)a->ptr + a->len, p, len);
|
||||||
|
a->len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_bytes_from_str(FArr *out, const FStr *s) {
|
||||||
|
out->len = out->cap = s->len;
|
||||||
|
out->ptr = s->len ? finch_alloc(s->len) : NULL;
|
||||||
|
if (s->len) memcpy(out->ptr, s->ptr, (size_t)s->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- program arguments, input and files ----------
|
||||||
|
|
||||||
|
void finch_args(FArr *out, int argc, char **argv) {
|
||||||
|
out->len = out->cap = argc;
|
||||||
|
out->ptr = finch_alloc((int64_t)sizeof(FStr) * (argc ? argc : 1));
|
||||||
|
for (int i = 0; i < argc; i++) str_heap((FStr *)out->ptr + i, argv[i], (int64_t)strlen(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// input("Name? ") prints the prompt and reads one line, without the newline.
|
||||||
|
void finch_input(FStr *out, const FStr *prompt) {
|
||||||
|
if (prompt->len) fwrite(prompt->ptr, 1, (size_t)prompt->len, stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
char *line = NULL;
|
||||||
|
size_t size = 0;
|
||||||
|
ssize_t n = getline(&line, &size, stdin);
|
||||||
|
if (n < 0) {
|
||||||
|
free(line);
|
||||||
|
str_static(out, empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0;
|
||||||
|
out->ptr = line;
|
||||||
|
out->len = n;
|
||||||
|
out->cap = (int64_t)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_file_exists(const FStr *path) {
|
||||||
|
FILE *f = fopen(path->ptr, "rb");
|
||||||
|
if (f) fclose(f);
|
||||||
|
return f != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_read_file(FStr *out, const FStr *path, const char *file, int64_t line) {
|
||||||
|
FILE *f = fopen(path->ptr, "rb");
|
||||||
|
if (!f) {
|
||||||
|
char msg[300];
|
||||||
|
snprintf(msg, sizeof msg, "can't read the file \"%.*s\": %s", (int)(path->len > 200 ? 200 : path->len),
|
||||||
|
path->ptr, strerror(errno));
|
||||||
|
finch_panic(file, line, msg);
|
||||||
|
}
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long size = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
char *m = finch_alloc(size + 1);
|
||||||
|
size_t got = fread(m, 1, (size_t)size, f);
|
||||||
|
fclose(f);
|
||||||
|
m[got] = 0;
|
||||||
|
out->ptr = m;
|
||||||
|
out->len = (int64_t)got;
|
||||||
|
out->cap = size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_write_file(const FStr *path, const FStr *text) {
|
||||||
|
FILE *f = fopen(path->ptr, "wb");
|
||||||
|
if (!f) return 0;
|
||||||
|
size_t put = fwrite(text->ptr, 1, (size_t)text->len, f);
|
||||||
|
return fclose(f) == 0 && put == (size_t)text->len;
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
# building Finch
|
# building Finch (the clang wrapper first, so `clang` finds the system headers)
|
||||||
|
llvmPackages.clang
|
||||||
llvmPackages.llvm
|
llvmPackages.llvm
|
||||||
llvmPackages.libclang # reads C headers for `import "stdio.h"`
|
llvmPackages.libclang # reads C headers for `import "stdio.h"`
|
||||||
llvmPackages.clang
|
|
||||||
cmake
|
cmake
|
||||||
ninja
|
ninja
|
||||||
pkg-config # finds the right flags for `link "..."`
|
pkg-config # finds the right flags for `link "..."`
|
||||||
|
|
|
||||||
309
src/abi.cpp
Normal file
309
src/abi.cpp
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
// The C calling convention for structs passed and returned by value (System V x86-64),
|
||||||
|
// the way clang does it: small structs travel in registers, split into eightbytes that are
|
||||||
|
// either INTEGER (general registers) or SSE (xmm registers); bigger ones go through memory.
|
||||||
|
|
||||||
|
#include "codegen_impl.h"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
|
||||||
|
namespace finch {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum Class { None, Integer, Sse };
|
||||||
|
|
||||||
|
struct Leaf {
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t size;
|
||||||
|
bool isFloat;
|
||||||
|
bool isDouble;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Flatten a value into its scalar pieces with byte offsets.
|
||||||
|
static void leaves(Codegen &cg, const FType &t, uint64_t base, std::vector<Leaf> &out) {
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Struct: {
|
||||||
|
const StructLayout *lay = cg.dl->getStructLayout(t.info->llvm);
|
||||||
|
for (auto &f : t.info->fields) leaves(cg, f.type, base + lay->getElementOffset(f.llvmIndex), out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FType::Fixed: {
|
||||||
|
uint64_t es = cg.dl->getTypeAllocSize(cg.ty(*t.elem));
|
||||||
|
for (long long i = 0; i < t.count; i++) leaves(cg, *t.elem, base + i * es, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
out.push_back({base, cg.dl->getTypeAllocSize(cg.ty(t)), t.kind == FType::F32 || t.kind == FType::F64,
|
||||||
|
t.kind == FType::F64});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiArg Codegen::classify(const FType &t) {
|
||||||
|
AbiArg a;
|
||||||
|
if (t.kind != FType::Struct) return a; // Direct
|
||||||
|
uint64_t size = dl->getTypeAllocSize(ty(t));
|
||||||
|
if (size > 16 || size == 0) {
|
||||||
|
a.kind = AbiArg::Memory;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
std::vector<Leaf> ls;
|
||||||
|
leaves(*this, t, 0, ls);
|
||||||
|
unsigned n = (size + 7) / 8;
|
||||||
|
Class cls[2] = {None, None};
|
||||||
|
bool hasDouble[2] = {false, false};
|
||||||
|
for (const Leaf &l : ls) {
|
||||||
|
if (l.offset % std::min<uint64_t>(l.size, 8) != 0) { // misaligned field (packed struct): memory
|
||||||
|
a.kind = AbiArg::Memory;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
unsigned k = l.offset / 8;
|
||||||
|
cls[k] = (cls[k] == Integer || !l.isFloat) ? Integer : Sse;
|
||||||
|
hasDouble[k] |= l.isDouble;
|
||||||
|
}
|
||||||
|
a.kind = AbiArg::Expand;
|
||||||
|
for (unsigned k = 0; k < n; k++) {
|
||||||
|
uint64_t bytes = std::min<uint64_t>(8, size - 8 * k);
|
||||||
|
if (cls[k] == Sse) {
|
||||||
|
if (hasDouble[k]) a.parts.push_back(b.getDoubleTy());
|
||||||
|
else if (bytes > 4) a.parts.push_back(FixedVectorType::get(b.getFloatTy(), 2));
|
||||||
|
else a.parts.push_back(b.getFloatTy());
|
||||||
|
} else {
|
||||||
|
a.parts.push_back(b.getIntNTy(bytes * 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.coerced = a.parts.size() == 1 ? a.parts[0] : StructType::get(ctx, a.parts);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Plan {
|
||||||
|
AbiArg ret;
|
||||||
|
std::vector<AbiArg> params;
|
||||||
|
llvm::FunctionType *fty = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Decide how every parameter travels, counting the 6 integer and 8 SSE argument registers.
|
||||||
|
static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> ¶ms, bool variadic,
|
||||||
|
std::vector<llvm::Type *> *extraTypes = nullptr) {
|
||||||
|
Plan pl;
|
||||||
|
int intRegs = 6, sseRegs = 8;
|
||||||
|
std::vector<llvm::Type *> types;
|
||||||
|
llvm::Type *retTy = cg.ty(ret);
|
||||||
|
if (ret.kind == FType::Str) retTy = cg.b.getPtrTy();
|
||||||
|
pl.ret = cg.classify(ret);
|
||||||
|
if (pl.ret.kind == AbiArg::Memory) {
|
||||||
|
types.push_back(cg.b.getPtrTy()); // sret
|
||||||
|
retTy = cg.b.getVoidTy();
|
||||||
|
intRegs--;
|
||||||
|
} else if (pl.ret.kind == AbiArg::Expand) {
|
||||||
|
retTy = pl.ret.coerced;
|
||||||
|
}
|
||||||
|
for (const FType &t : params) {
|
||||||
|
AbiArg a = cg.classify(t);
|
||||||
|
if (a.kind == AbiArg::Expand) {
|
||||||
|
int ni = 0, ns = 0;
|
||||||
|
for (llvm::Type *p : a.parts) (p->isIntegerTy() ? ni : ns)++;
|
||||||
|
if (ni <= intRegs && ns <= sseRegs) {
|
||||||
|
intRegs -= ni;
|
||||||
|
sseRegs -= ns;
|
||||||
|
for (llvm::Type *p : a.parts) types.push_back(p);
|
||||||
|
} else {
|
||||||
|
a.kind = AbiArg::Memory; // out of registers: the whole struct goes on the stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.kind == AbiArg::Memory) types.push_back(cg.b.getPtrTy());
|
||||||
|
if (a.kind == AbiArg::Direct) {
|
||||||
|
if (t.isFloat()) sseRegs--;
|
||||||
|
else intRegs--;
|
||||||
|
types.push_back(t.kind == FType::Str ? cg.b.getPtrTy() : cg.ty(t));
|
||||||
|
}
|
||||||
|
pl.params.push_back(a);
|
||||||
|
}
|
||||||
|
(void)extraTypes;
|
||||||
|
pl.fty = FunctionType::get(retTy, types, variadic);
|
||||||
|
return pl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Attribute::AttrKind extAttr(const FType &t) {
|
||||||
|
if (t.bits() >= 32 || !(t.isInt() || t.kind == FType::Bool || t.kind == FType::Char)) return Attribute::None;
|
||||||
|
return t.isSigned() || t.kind == FType::Char ? Attribute::SExt : Attribute::ZExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes that tell LLVM how C passes each piece: sret, byval, signext/zeroext.
|
||||||
|
static AttributeList attrsFor(Codegen &cg, const Plan &pl, const FType &ret, const std::vector<FType> ¶ms) {
|
||||||
|
LLVMContext &ctx = cg.ctx;
|
||||||
|
AttributeList al;
|
||||||
|
unsigned idx = 0;
|
||||||
|
if (pl.ret.kind == AbiArg::Memory) {
|
||||||
|
al = al.addParamAttribute(ctx, idx, Attribute::getWithStructRetType(ctx, cg.ty(ret)));
|
||||||
|
idx++;
|
||||||
|
} else if (Attribute::AttrKind k = extAttr(ret); k != Attribute::None) {
|
||||||
|
al = al.addRetAttribute(ctx, k);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < params.size(); i++) {
|
||||||
|
const AbiArg &a = pl.params[i];
|
||||||
|
if (a.kind == AbiArg::Memory) {
|
||||||
|
uint64_t align = std::max<uint64_t>(8, params[i].info->isC ? params[i].info->cdecl->align : 8);
|
||||||
|
al = al.addParamAttribute(ctx, idx, Attribute::getWithByValType(ctx, cg.ty(params[i])));
|
||||||
|
al = al.addParamAttribute(ctx, idx, Attribute::getWithAlignment(ctx, Align(align)));
|
||||||
|
idx++;
|
||||||
|
} else if (a.kind == AbiArg::Expand) {
|
||||||
|
idx += a.parts.size();
|
||||||
|
} else {
|
||||||
|
if (Attribute::AttrKind k = extAttr(params[i]); k != Attribute::None) al = al.addParamAttribute(ctx, idx, k);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return al;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<FType> resolvedParams(Codegen &cg, const CFunc &f) {
|
||||||
|
std::vector<FType> ps;
|
||||||
|
for (const FType &t : f.params) ps.push_back(cg.resolve(t, Pos{}));
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function *Codegen::declareC(const CFunc &f) {
|
||||||
|
if (Function *fn = mod->getFunction(f.name)) return fn;
|
||||||
|
FType ret = resolve(f.ret, Pos{});
|
||||||
|
std::vector<FType> ps = resolvedParams(*this, f);
|
||||||
|
for (const FType &t : ps)
|
||||||
|
if (t.kind == FType::Struct && !t.info->unsupported.empty()) return nullptr;
|
||||||
|
Plan pl = makePlan(*this, ret, ps, f.variadic);
|
||||||
|
Function *fn = Function::Create(pl.fty, Function::ExternalLinkage, f.name, mod.get());
|
||||||
|
fn->setAttributes(attrsFor(*this, pl, ret, ps));
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A stack slot big enough for both the struct and its register form.
|
||||||
|
static Value *abiSlot(Codegen &cg, const FType &t, const AbiArg &a) {
|
||||||
|
llvm::Type *st = cg.ty(t);
|
||||||
|
llvm::Type *big = a.coerced && cg.dl->getTypeAllocSize(a.coerced) > cg.dl->getTypeAllocSize(st) ? a.coerced : st;
|
||||||
|
return cg.tmpOf(big);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::emitCCall(const CFunc &f, Function *fn, std::vector<Value_> args, Pos p) {
|
||||||
|
FType ret = resolve(f.ret, p);
|
||||||
|
std::vector<FType> ps = resolvedParams(*this, f);
|
||||||
|
for (const FType &t : ps)
|
||||||
|
if (t.kind == FType::Struct && !t.info->unsupported.empty())
|
||||||
|
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' takes the struct " + t.info->name +
|
||||||
|
" by value, which Finch can't pass yet: it " + t.info->unsupported);
|
||||||
|
if (ret.kind == FType::Struct && !ret.info->unsupported.empty())
|
||||||
|
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' returns the struct " + ret.info->name +
|
||||||
|
" by value, which Finch can't receive yet: it " + ret.info->unsupported);
|
||||||
|
Plan pl = makePlan(*this, ret, ps, f.variadic);
|
||||||
|
|
||||||
|
std::vector<Value *> ll;
|
||||||
|
Value *sret = nullptr;
|
||||||
|
if (pl.ret.kind == AbiArg::Memory) {
|
||||||
|
sret = tmpOf(ty(ret));
|
||||||
|
cast<AllocaInst>(sret)->setAlignment(Align(16));
|
||||||
|
ll.push_back(sret);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
|
const Value_ &v = args[i];
|
||||||
|
if (i >= ps.size()) { // the '...' part: C's default promotions
|
||||||
|
const FType &t = v.type;
|
||||||
|
Value *x = v.v;
|
||||||
|
if (t.kind == FType::F32) x = b.CreateFPExt(x, b.getDoubleTy());
|
||||||
|
else if (t.kind == FType::Str) x = cstr(x);
|
||||||
|
else if (t.kind == FType::Bool || ((t.isInt() || t.kind == FType::Char) && t.bits() < 32))
|
||||||
|
x = b.CreateIntCast(x, b.getInt32Ty(), t.isSigned() || t.kind == FType::Char);
|
||||||
|
ll.push_back(x);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const AbiArg &a = pl.params[i];
|
||||||
|
if (ps[i].kind == FType::Str) {
|
||||||
|
ll.push_back(cstr(v.v));
|
||||||
|
} else if (a.kind == AbiArg::Memory) {
|
||||||
|
Value *s = tmpOf(ty(ps[i]));
|
||||||
|
cast<AllocaInst>(s)->setAlignment(Align(16));
|
||||||
|
b.CreateStore(v.v, s);
|
||||||
|
ll.push_back(s);
|
||||||
|
} else if (a.kind == AbiArg::Expand) {
|
||||||
|
Value *s = abiSlot(*this, ps[i], a);
|
||||||
|
b.CreateStore(v.v, s);
|
||||||
|
for (size_t k = 0; k < a.parts.size(); k++)
|
||||||
|
ll.push_back(b.CreateLoad(a.parts[k], b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k)));
|
||||||
|
} else {
|
||||||
|
ll.push_back(v.v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CallInst *call = b.CreateCall(pl.fty, fn, ll);
|
||||||
|
AttributeList al = attrsFor(*this, pl, ret, ps);
|
||||||
|
call->setAttributes(al);
|
||||||
|
|
||||||
|
if (ret.kind == FType::Void) return {nullptr, FType::Void};
|
||||||
|
if (pl.ret.kind == AbiArg::Memory) return {b.CreateLoad(ty(ret), sret), ret};
|
||||||
|
if (pl.ret.kind == AbiArg::Expand) {
|
||||||
|
Value *s = abiSlot(*this, ret, pl.ret);
|
||||||
|
b.CreateStore(call, s);
|
||||||
|
return {b.CreateLoad(ty(ret), s), ret};
|
||||||
|
}
|
||||||
|
if (ret.kind == FType::Str) return strFromC(call); // borrowed: copied if it is kept
|
||||||
|
return {call, ret};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A C-callable door into a Finch function, for addr(fn) (callbacks like qsort's or libclang's).
|
||||||
|
Function *Codegen::cThunk(Fn &fn, Pos p) {
|
||||||
|
if (fn.cThunk) return fn.cThunk;
|
||||||
|
auto cOk = [](const FType &t) {
|
||||||
|
return t.isNumber() || t.kind == FType::Bool || t.kind == FType::Char || t.isPtr() || t.kind == FType::Void ||
|
||||||
|
(t.kind == FType::Struct && t.info->isC);
|
||||||
|
};
|
||||||
|
for (const FType &t : fn.params)
|
||||||
|
if (!cOk(t))
|
||||||
|
failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + ") gives C a function pointer, so its parameters must be C types (numbers, ptr, C structs), not " + t.show());
|
||||||
|
if (!cOk(fn.ret)) failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + "): a function given to C can't return " + fn.ret.show());
|
||||||
|
|
||||||
|
Plan pl = makePlan(*this, fn.ret, fn.params, false);
|
||||||
|
Function *th = Function::Create(pl.fty, Function::InternalLinkage, "finch.c." + fn.decl->name, mod.get());
|
||||||
|
th->setAttributes(attrsFor(*this, pl, fn.ret, fn.params));
|
||||||
|
fn.cThunk = th;
|
||||||
|
|
||||||
|
IRBuilderBase::InsertPointGuard guard(b);
|
||||||
|
DISubprogram *savedFn = diFn;
|
||||||
|
diFn = nullptr;
|
||||||
|
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", th));
|
||||||
|
b.SetCurrentDebugLocation(DebugLoc());
|
||||||
|
unsigned ai = 0;
|
||||||
|
Value *sret = nullptr;
|
||||||
|
if (pl.ret.kind == AbiArg::Memory) sret = th->getArg(ai++);
|
||||||
|
std::vector<Value *> args;
|
||||||
|
for (size_t i = 0; i < fn.params.size(); i++) {
|
||||||
|
const AbiArg &a = pl.params[i];
|
||||||
|
if (a.kind == AbiArg::Memory) {
|
||||||
|
args.push_back(b.CreateLoad(ty(fn.params[i]), th->getArg(ai++)));
|
||||||
|
} else if (a.kind == AbiArg::Expand) {
|
||||||
|
Value *s = abiSlot(*this, fn.params[i], a);
|
||||||
|
for (size_t k = 0; k < a.parts.size(); k++)
|
||||||
|
b.CreateStore(th->getArg(ai++), b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k));
|
||||||
|
args.push_back(b.CreateLoad(ty(fn.params[i]), s));
|
||||||
|
} else {
|
||||||
|
args.push_back(th->getArg(ai++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value *r = b.CreateCall(fn.llvm, args);
|
||||||
|
if (fn.ret.kind == FType::Void) {
|
||||||
|
b.CreateRetVoid();
|
||||||
|
} else if (pl.ret.kind == AbiArg::Memory) {
|
||||||
|
b.CreateStore(r, sret);
|
||||||
|
b.CreateRetVoid();
|
||||||
|
} else if (pl.ret.kind == AbiArg::Expand) {
|
||||||
|
Value *s = abiSlot(*this, fn.ret, pl.ret);
|
||||||
|
b.CreateStore(r, s);
|
||||||
|
b.CreateRet(b.CreateLoad(pl.ret.coerced, s));
|
||||||
|
} else {
|
||||||
|
b.CreateRet(r);
|
||||||
|
}
|
||||||
|
diFn = savedFn;
|
||||||
|
return th;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace finch
|
||||||
99
src/ast.h
99
src/ast.h
|
|
@ -4,10 +4,23 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
struct StructInfo; // filled in by codegen: fields, LLVM type, ownership
|
||||||
|
|
||||||
struct Type {
|
struct Type {
|
||||||
enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Str, Ptr, Null };
|
enum Kind {
|
||||||
|
Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
|
||||||
|
Str, Ptr, Null,
|
||||||
|
Array, // []T: growable, owns its elements
|
||||||
|
Fixed, // [N]T: only inside C structs
|
||||||
|
Struct, // a resolved struct
|
||||||
|
Named, // a struct name as written, before codegen resolves it
|
||||||
|
};
|
||||||
Kind kind = Void;
|
Kind kind = Void;
|
||||||
std::shared_ptr<Type> elem; // what a Ptr points to; null for the untyped `ptr`
|
std::shared_ptr<Type> elem; // Ptr (null = untyped `ptr`), Array, Fixed
|
||||||
|
long long count = 0; // Fixed
|
||||||
|
std::string name; // Named / Struct
|
||||||
|
std::string module; // Named: written qualifier ("" = none); Struct: defining module
|
||||||
|
StructInfo *info = nullptr; // Struct
|
||||||
|
|
||||||
Type() = default;
|
Type() = default;
|
||||||
Type(Kind k) : kind(k) {}
|
Type(Kind k) : kind(k) {}
|
||||||
|
|
@ -16,6 +29,11 @@ struct Type {
|
||||||
p.elem = std::make_shared<Type>(t);
|
p.elem = std::make_shared<Type>(t);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
static Type arrayOf(const Type &t) {
|
||||||
|
Type a(Array);
|
||||||
|
a.elem = std::make_shared<Type>(t);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
bool isInt() const { return kind >= I8 && kind <= U64; }
|
bool isInt() const { return kind >= I8 && kind <= U64; }
|
||||||
bool isSigned() const { return kind >= I8 && kind <= I64; }
|
bool isSigned() const { return kind >= I8 && kind <= I64; }
|
||||||
|
|
@ -35,13 +53,21 @@ struct Type {
|
||||||
|
|
||||||
bool operator==(const Type &o) const {
|
bool operator==(const Type &o) const {
|
||||||
if (kind != o.kind) return false;
|
if (kind != o.kind) return false;
|
||||||
if (kind != Ptr) return true;
|
switch (kind) {
|
||||||
|
case Ptr:
|
||||||
if (!elem || !o.elem) return !elem && !o.elem;
|
if (!elem || !o.elem) return !elem && !o.elem;
|
||||||
return *elem == *o.elem;
|
return *elem == *o.elem;
|
||||||
|
case Array: return *elem == *o.elem;
|
||||||
|
case Fixed: return count == o.count && *elem == *o.elem;
|
||||||
|
case Struct: return info == o.info;
|
||||||
|
case Named: return name == o.name && module == o.module;
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool operator!=(const Type &o) const { return !(*this == o); }
|
bool operator!=(const Type &o) const { return !(*this == o); }
|
||||||
|
|
||||||
std::string name() const {
|
// How the type is written in Finch, for error messages.
|
||||||
|
std::string show() const {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case Void: return "nothing";
|
case Void: return "nothing";
|
||||||
case Bool: return "bool";
|
case Bool: return "bool";
|
||||||
|
|
@ -57,8 +83,12 @@ struct Type {
|
||||||
case F32: return "f32";
|
case F32: return "f32";
|
||||||
case F64: return "float";
|
case F64: return "float";
|
||||||
case Str: return "str";
|
case Str: return "str";
|
||||||
case Ptr: return elem ? "ptr[" + elem->name() + "]" : "ptr";
|
case Ptr: return elem ? "ptr[" + elem->show() + "]" : "ptr";
|
||||||
case Null: return "null";
|
case Null: return "null";
|
||||||
|
case Array: return "[]" + elem->show();
|
||||||
|
case Fixed: return "[" + std::to_string(count) + "]" + elem->show();
|
||||||
|
case Struct:
|
||||||
|
case Named: return module.empty() ? name : module + "." + name;
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +99,12 @@ bool typeFromName(const std::string &name, Type &out);
|
||||||
|
|
||||||
struct Pos {
|
struct Pos {
|
||||||
int line = 0, col = 0;
|
int line = 0, col = 0;
|
||||||
|
int file = 0; // index into the list of source files (see error.h)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- expressions ----------
|
// ---------- expressions ----------
|
||||||
|
|
||||||
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member };
|
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member, Index, ArrayLit, Method };
|
||||||
|
|
||||||
struct Expr {
|
struct Expr {
|
||||||
ExprKind kind;
|
ExprKind kind;
|
||||||
|
|
@ -127,21 +158,39 @@ struct BinaryExpr : Expr {
|
||||||
BinaryExpr(Pos p, BinOp o, ExprPtr l, ExprPtr r)
|
BinaryExpr(Pos p, BinOp o, ExprPtr l, ExprPtr r)
|
||||||
: Expr(ExprKind::Binary, p), op(o), lhs(std::move(l)), rhs(std::move(r)) {}
|
: Expr(ExprKind::Binary, p), op(o), lhs(std::move(l)), rhs(std::move(r)) {}
|
||||||
};
|
};
|
||||||
|
// name(args): a function, a built-in, a conversion or a struct constructor.
|
||||||
struct CallExpr : Expr {
|
struct CallExpr : Expr {
|
||||||
std::string callee;
|
std::string callee;
|
||||||
std::vector<ExprPtr> args;
|
std::vector<ExprPtr> args;
|
||||||
|
std::vector<std::string> argNames; // "" for positional; Point(x: 1, y: 2)
|
||||||
CallExpr(Pos p, std::string c) : Expr(ExprKind::Call, p), callee(std::move(c)) {}
|
CallExpr(Pos p, std::string c) : Expr(ExprKind::Call, p), callee(std::move(c)) {}
|
||||||
};
|
};
|
||||||
struct MemberExpr : Expr { // obj.field (for now only p.value)
|
struct MemberExpr : Expr { // obj.field, p.value, a.len
|
||||||
ExprPtr obj;
|
ExprPtr obj;
|
||||||
std::string field;
|
std::string field;
|
||||||
MemberExpr(Pos p, ExprPtr o, std::string f)
|
MemberExpr(Pos p, ExprPtr o, std::string f)
|
||||||
: Expr(ExprKind::Member, p), obj(std::move(o)), field(std::move(f)) {}
|
: Expr(ExprKind::Member, p), obj(std::move(o)), field(std::move(f)) {}
|
||||||
};
|
};
|
||||||
|
struct IndexExpr : Expr { // a[i]
|
||||||
|
ExprPtr obj, index;
|
||||||
|
IndexExpr(Pos p, ExprPtr o, ExprPtr i) : Expr(ExprKind::Index, p), obj(std::move(o)), index(std::move(i)) {}
|
||||||
|
};
|
||||||
|
struct ArrayLitExpr : Expr { // [1, 2, 3]
|
||||||
|
std::vector<ExprPtr> elems;
|
||||||
|
explicit ArrayLitExpr(Pos p) : Expr(ExprKind::ArrayLit, p) {}
|
||||||
|
};
|
||||||
|
// obj.name(args): a.push(x), s.find("x"), and math.sqrt(2) / math.Vec(1, 2) for modules
|
||||||
|
struct MethodExpr : Expr {
|
||||||
|
ExprPtr obj;
|
||||||
|
std::string name;
|
||||||
|
std::vector<ExprPtr> args;
|
||||||
|
std::vector<std::string> argNames;
|
||||||
|
MethodExpr(Pos p, ExprPtr o, std::string n) : Expr(ExprKind::Method, p), obj(std::move(o)), name(std::move(n)) {}
|
||||||
|
};
|
||||||
|
|
||||||
// ---------- statements ----------
|
// ---------- statements ----------
|
||||||
|
|
||||||
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, Return, Break, Continue, Block };
|
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, ForEach, Return, Break, Continue, Block, Defer };
|
||||||
|
|
||||||
struct Stmt {
|
struct Stmt {
|
||||||
StmtKind kind;
|
StmtKind kind;
|
||||||
|
|
@ -165,7 +214,7 @@ struct VarDeclStmt : Stmt {
|
||||||
VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {}
|
VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {}
|
||||||
};
|
};
|
||||||
struct AssignStmt : Stmt {
|
struct AssignStmt : Stmt {
|
||||||
ExprPtr target; // a variable or p.value
|
ExprPtr target; // a variable, field, element or p.value
|
||||||
char op; // '=', '+', '-', '*', '/', '%'
|
char op; // '=', '+', '-', '*', '/', '%'
|
||||||
ExprPtr value;
|
ExprPtr value;
|
||||||
AssignStmt(Pos p) : Stmt(StmtKind::Assign, p) {}
|
AssignStmt(Pos p) : Stmt(StmtKind::Assign, p) {}
|
||||||
|
|
@ -191,6 +240,12 @@ struct ForStmt : Stmt {
|
||||||
BlockPtr body;
|
BlockPtr body;
|
||||||
ForStmt(Pos p) : Stmt(StmtKind::For, p) {}
|
ForStmt(Pos p) : Stmt(StmtKind::For, p) {}
|
||||||
};
|
};
|
||||||
|
struct ForEachStmt : Stmt {
|
||||||
|
std::string var;
|
||||||
|
ExprPtr list; // for var in list (an array or a str)
|
||||||
|
BlockPtr body;
|
||||||
|
ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {}
|
||||||
|
};
|
||||||
struct ReturnStmt : Stmt {
|
struct ReturnStmt : Stmt {
|
||||||
ExprPtr value; // may be null
|
ExprPtr value; // may be null
|
||||||
ReturnStmt(Pos p) : Stmt(StmtKind::Return, p) {}
|
ReturnStmt(Pos p) : Stmt(StmtKind::Return, p) {}
|
||||||
|
|
@ -201,6 +256,10 @@ struct BreakStmt : Stmt {
|
||||||
struct ContinueStmt : Stmt {
|
struct ContinueStmt : Stmt {
|
||||||
explicit ContinueStmt(Pos p) : Stmt(StmtKind::Continue, p) {}
|
explicit ContinueStmt(Pos p) : Stmt(StmtKind::Continue, p) {}
|
||||||
};
|
};
|
||||||
|
struct DeferStmt : Stmt { // defer <statement>: runs when the block ends
|
||||||
|
StmtPtr body;
|
||||||
|
DeferStmt(Pos p, StmtPtr s) : Stmt(StmtKind::Defer, p), body(std::move(s)) {}
|
||||||
|
};
|
||||||
|
|
||||||
// ---------- top level ----------
|
// ---------- top level ----------
|
||||||
|
|
||||||
|
|
@ -218,18 +277,36 @@ struct FnDecl {
|
||||||
BlockPtr body;
|
BlockPtr body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
Type type;
|
||||||
|
std::string name;
|
||||||
|
ExprPtr init; // default value, may be null
|
||||||
|
Pos pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StructDecl {
|
||||||
|
Pos pos;
|
||||||
|
std::string name;
|
||||||
|
std::vector<Field> fields;
|
||||||
|
};
|
||||||
|
|
||||||
struct Import {
|
struct Import {
|
||||||
Pos pos;
|
Pos pos;
|
||||||
std::string path; // "stdio.h" for C headers
|
std::string path; // "stdio.h" (C header, isC) or "math" (Finch module)
|
||||||
|
bool isC = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Link {
|
struct Link {
|
||||||
Pos pos;
|
Pos pos;
|
||||||
std::string lib; // "glfw" -> libglfw.so
|
std::string lib; // "glfw" -> libglfw.so; "helpers.c" / ".o" / ".a" -> a file next to the .fn file
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// One .fn file.
|
||||||
struct Program {
|
struct Program {
|
||||||
|
std::string module; // "" for the main file, else the module's name
|
||||||
|
std::string path;
|
||||||
std::vector<Import> imports;
|
std::vector<Import> imports;
|
||||||
std::vector<Link> links;
|
std::vector<Link> links;
|
||||||
|
std::vector<StructDecl> structs;
|
||||||
std::vector<FnDecl> fns;
|
std::vector<FnDecl> fns;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
215
src/cimport.cpp
215
src/cimport.cpp
|
|
@ -41,26 +41,97 @@ std::string str(CXString s) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// C type -> Finch type. Returns false (with a reason) if Finch can't express it yet.
|
// Where a C type is used decides how `char *` looks in Finch: in function signatures it is
|
||||||
bool mapType(CXType t, Type &out, std::string &why) {
|
// a `str`; inside structs, globals and behind pointers it stays a raw `ptr[char]`.
|
||||||
|
enum class Ctx { Signature, Data };
|
||||||
|
|
||||||
|
struct Importer {
|
||||||
|
CImports &out;
|
||||||
|
CXTranslationUnit tu = nullptr;
|
||||||
|
std::string header;
|
||||||
|
std::map<std::string, std::string> recordNames; // record USR -> Finch name (typedef wins)
|
||||||
|
std::map<std::string, int> defining; // USR -> 1 while its fields are being mapped
|
||||||
|
int anon = 0;
|
||||||
|
|
||||||
|
explicit Importer(CImports &o) : out(o) {}
|
||||||
|
|
||||||
|
// The Finch name of a C record: its typedef name, its tag, or a made-up one.
|
||||||
|
std::string recordName(CXType t) {
|
||||||
|
CXCursor decl = clang_getTypeDeclaration(t);
|
||||||
|
std::string usr = str(clang_getCursorUSR(decl));
|
||||||
|
auto it = recordNames.find(usr);
|
||||||
|
if (it != recordNames.end()) return it->second;
|
||||||
|
std::string name = str(clang_getCursorSpelling(decl));
|
||||||
|
if (name.empty() || name.find(' ') != std::string::npos) name = "anon_struct_" + std::to_string(++anon);
|
||||||
|
recordNames[usr] = name;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill out.structs[name] for a complete record; returns its name, or "" if it can't be one.
|
||||||
|
std::string defineRecord(CXType t) {
|
||||||
|
t = clang_getCanonicalType(t);
|
||||||
|
CXCursor decl = clang_getTypeDeclaration(t);
|
||||||
|
std::string usr = str(clang_getCursorUSR(decl));
|
||||||
|
std::string name = recordName(t);
|
||||||
|
if (out.structs.count(name) || defining.count(usr)) return name;
|
||||||
|
long long size = clang_Type_getSizeOf(t);
|
||||||
|
if (size < 0) return ""; // incomplete (opaque) struct
|
||||||
|
defining[usr] = 1;
|
||||||
|
|
||||||
|
CStruct s;
|
||||||
|
s.name = name;
|
||||||
|
s.size = size;
|
||||||
|
s.align = clang_Type_getAlignOf(t);
|
||||||
|
if (clang_getCursorKind(decl) == CXCursor_UnionDecl) s.unsupported = "is a C union";
|
||||||
|
struct Ctx2 { Importer *self; CStruct *s; } c2{this, &s};
|
||||||
|
clang_Type_visitFields(t, [](CXCursor field, CXClientData data) -> CXVisitorResult {
|
||||||
|
auto &c = *static_cast<Ctx2 *>(data);
|
||||||
|
CField f;
|
||||||
|
f.name = str(clang_getCursorSpelling(field));
|
||||||
|
CXType ft = clang_getCanonicalType(clang_getCursorType(field));
|
||||||
|
f.offset = clang_Cursor_getOffsetOfField(field) / 8;
|
||||||
|
f.size = clang_Type_getSizeOf(ft);
|
||||||
|
f.align = clang_Type_getAlignOf(ft);
|
||||||
|
std::string why;
|
||||||
|
if (clang_Cursor_isBitField(field)) {
|
||||||
|
c.s->unsupported = "has bit fields";
|
||||||
|
f.hidden = true;
|
||||||
|
} else if (ft.kind == CXType_Record &&
|
||||||
|
clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl) {
|
||||||
|
f.hidden = true; // unions are kept as opaque bytes
|
||||||
|
} else if (!c.self->mapType(ft, f.type, why, Ctx::Data)) {
|
||||||
|
f.hidden = true;
|
||||||
|
}
|
||||||
|
if (f.hidden) c.s->hasHidden = true;
|
||||||
|
if (f.name.empty()) f.hidden = true;
|
||||||
|
c.s->fields.push_back(f);
|
||||||
|
return CXVisit_Continue;
|
||||||
|
}, &c2);
|
||||||
|
defining.erase(usr);
|
||||||
|
out.structs[name] = s;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// C type -> Finch type. Returns false (with a reason) if Finch can't express it.
|
||||||
|
bool mapType(CXType t, Type &res, std::string &why, Ctx ctx) {
|
||||||
t = clang_getCanonicalType(t);
|
t = clang_getCanonicalType(t);
|
||||||
long long size = clang_Type_getSizeOf(t);
|
long long size = clang_Type_getSizeOf(t);
|
||||||
auto sized = [&](bool isSigned) {
|
auto sized = [&](bool isSigned) {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: out = isSigned ? Type::I8 : Type::U8; return true;
|
case 1: res = isSigned ? Type::I8 : Type::U8; return true;
|
||||||
case 2: out = isSigned ? Type::I16 : Type::U16; return true;
|
case 2: res = isSigned ? Type::I16 : Type::U16; return true;
|
||||||
case 4: out = isSigned ? Type::I32 : Type::U32; return true;
|
case 4: res = isSigned ? Type::I32 : Type::U32; return true;
|
||||||
case 8: out = isSigned ? Type::I64 : Type::U64; return true;
|
case 8: res = isSigned ? Type::I64 : Type::U64; return true;
|
||||||
}
|
}
|
||||||
why = "uses a " + std::to_string(size * 8) + "-bit number";
|
why = "uses a " + std::to_string(size * 8) + "-bit number";
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (t.kind) {
|
switch (t.kind) {
|
||||||
case CXType_Void: out = Type::Void; return true;
|
case CXType_Void: res = Type::Void; return true;
|
||||||
case CXType_Bool: out = Type::Bool; return true;
|
case CXType_Bool: res = Type::Bool; return true;
|
||||||
case CXType_Char_S:
|
case CXType_Char_S:
|
||||||
case CXType_Char_U: out = Type::Char; return true;
|
case CXType_Char_U: res = Type::Char; return true;
|
||||||
case CXType_SChar:
|
case CXType_SChar:
|
||||||
case CXType_Short:
|
case CXType_Short:
|
||||||
case CXType_Int:
|
case CXType_Int:
|
||||||
|
|
@ -72,31 +143,54 @@ bool mapType(CXType t, Type &out, std::string &why) {
|
||||||
case CXType_ULong:
|
case CXType_ULong:
|
||||||
case CXType_ULongLong: return sized(false);
|
case CXType_ULongLong: return sized(false);
|
||||||
case CXType_Enum: return sized(true);
|
case CXType_Enum: return sized(true);
|
||||||
case CXType_Float: out = Type::F32; return true;
|
case CXType_Float: res = Type::F32; return true;
|
||||||
case CXType_Double: out = Type::F64; return true;
|
case CXType_Double: res = Type::F64; return true;
|
||||||
case CXType_Pointer: {
|
case CXType_Pointer: {
|
||||||
CXType pointee = clang_getCanonicalType(clang_getPointeeType(t));
|
CXType pointee = clang_getCanonicalType(clang_getPointeeType(t));
|
||||||
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) {
|
if ((pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && ctx == Ctx::Signature) {
|
||||||
out = Type::Str; // char* is a C string
|
res = Type::Str; // char* in a signature is a C string
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Type inner;
|
Type inner;
|
||||||
std::string ignored;
|
std::string ignored;
|
||||||
// pointers to structs, functions, void...: an opaque `ptr` you can pass around
|
if (pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto ||
|
||||||
if (pointee.kind == CXType_Void || !mapType(pointee, inner, ignored) || inner.kind == Type::Void)
|
pointee.kind == CXType_FunctionNoProto || !mapType(pointee, inner, ignored, Ctx::Data) ||
|
||||||
out = Type::Ptr;
|
inner.kind == Type::Void || inner.kind == Type::Fixed)
|
||||||
|
res = Type::Ptr; // an opaque `ptr` you can pass around
|
||||||
else
|
else
|
||||||
out = Type::ptrTo(inner);
|
res = Type::ptrTo(inner);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case CXType_Record: {
|
||||||
|
if (clang_getCursorKind(clang_getTypeDeclaration(t)) == CXCursor_UnionDecl) {
|
||||||
|
why = "uses a C union by value";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string name = defineRecord(t);
|
||||||
|
if (name.empty()) {
|
||||||
|
why = "uses an incomplete C struct by value";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
res = Type(Type::Named);
|
||||||
|
res.name = name;
|
||||||
|
res.module = "C";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case CXType_ConstantArray: {
|
||||||
|
Type inner;
|
||||||
|
if (!mapType(clang_getArrayElementType(t), inner, why, Ctx::Data)) return false;
|
||||||
|
res = Type(Type::Fixed);
|
||||||
|
res.elem = std::make_shared<Type>(inner);
|
||||||
|
res.count = clang_getArraySize(t);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case CXType_Record: why = "passes a C struct by value (structs are coming)"; return false;
|
|
||||||
case CXType_LongDouble: why = "uses long double"; return false;
|
case CXType_LongDouble: why = "uses long double"; return false;
|
||||||
default: why = "uses the C type '" + str(clang_getTypeSpelling(t)) + "'"; return false;
|
default: why = "uses the C type '" + str(clang_getTypeSpelling(t)) + "'"; return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple #defines: a single number, maybe negative, maybe in parentheses.
|
// Simple #defines: a single number, maybe negative, maybe in parentheses.
|
||||||
bool macroValue(CXTranslationUnit tu, CXCursor c, CConst &out) {
|
bool macroValue(CXCursor c, CConst &res) {
|
||||||
CXToken *toks;
|
CXToken *toks;
|
||||||
unsigned n;
|
unsigned n;
|
||||||
clang_tokenize(tu, clang_getCursorExtent(c), &toks, &n);
|
clang_tokenize(tu, clang_getCursorExtent(c), &toks, &n);
|
||||||
|
|
@ -123,36 +217,39 @@ bool macroValue(CXTranslationUnit tu, CXCursor c, CConst &out) {
|
||||||
bool isFloat = lit.find_first_of(".eE") != std::string::npos && lit.rfind("0x", 0) != 0;
|
bool isFloat = lit.find_first_of(".eE") != std::string::npos && lit.rfind("0x", 0) != 0;
|
||||||
char *end;
|
char *end;
|
||||||
if (isFloat) {
|
if (isFloat) {
|
||||||
out.type = Type::F64;
|
res.type = Type::F64;
|
||||||
out.f = std::strtod(lit.c_str(), &end);
|
res.f = std::strtod(lit.c_str(), &end);
|
||||||
if (neg) out.f = -out.f;
|
if (neg) res.f = -res.f;
|
||||||
} else {
|
} else {
|
||||||
out.type = Type::I64;
|
res.type = Type::I64;
|
||||||
out.i = (long long)std::strtoull(lit.c_str(), &end, 0); // C rules: 0x.., 0755
|
res.i = (long long)std::strtoull(lit.c_str(), &end, 0); // C rules: 0x.., 0755
|
||||||
if (neg) out.i = -out.i;
|
if (neg) res.i = -res.i;
|
||||||
}
|
}
|
||||||
for (; *end; end++) // allow suffixes: 10UL, 1.0f
|
for (; *end; end++) // allow suffixes: 10UL, 1.0f
|
||||||
if (!std::strchr("uUlLfF", *end)) return false;
|
if (!std::strchr("uUlLfF", *end)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Visit {
|
void visitTop(CXCursor c) {
|
||||||
CImports *out;
|
|
||||||
CXTranslationUnit tu;
|
|
||||||
std::string header;
|
|
||||||
};
|
|
||||||
|
|
||||||
CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
|
|
||||||
Visit &v = *static_cast<Visit *>(data);
|
|
||||||
CImports &out = *v.out;
|
|
||||||
std::string name = str(clang_getCursorSpelling(c));
|
std::string name = str(clang_getCursorSpelling(c));
|
||||||
|
|
||||||
switch (clang_getCursorKind(c)) {
|
switch (clang_getCursorKind(c)) {
|
||||||
|
case CXCursor_TypedefDecl: {
|
||||||
|
// typedef struct {...} Name; -> the struct is called Name in Finch
|
||||||
|
CXType u = clang_getCanonicalType(clang_getTypedefDeclUnderlyingType(c));
|
||||||
|
if (u.kind == CXType_Record) {
|
||||||
|
std::string usr = str(clang_getCursorUSR(clang_getTypeDeclaration(u)));
|
||||||
|
// prefer the typedef name, unless the struct is already known under its tag
|
||||||
|
auto it = recordNames.find(usr);
|
||||||
|
bool used = it != recordNames.end() && out.structs.count(it->second);
|
||||||
|
if (!used && !out.structs.count(name)) recordNames[usr] = name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CXCursor_FunctionDecl: {
|
case CXCursor_FunctionDecl: {
|
||||||
if (out.fns.count(name)) break;
|
if (out.fns.count(name)) break;
|
||||||
CFunc f;
|
CFunc f;
|
||||||
f.name = name;
|
f.name = name;
|
||||||
f.header = v.header;
|
f.header = header;
|
||||||
if (clang_Cursor_getStorageClass(c) == CX_SC_Static) {
|
if (clang_Cursor_getStorageClass(c) == CX_SC_Static) {
|
||||||
f.unsupported = "is a static inline C function (it has no code to link against)";
|
f.unsupported = "is a static inline C function (it has no code to link against)";
|
||||||
out.fns[name] = f;
|
out.fns[name] = f;
|
||||||
|
|
@ -165,11 +262,11 @@ CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::string why;
|
std::string why;
|
||||||
if (!mapType(clang_getResultType(ft), f.ret, why)) f.unsupported = why;
|
if (!mapType(clang_getResultType(ft), f.ret, why, Ctx::Signature)) f.unsupported = why;
|
||||||
int n = clang_getNumArgTypes(ft);
|
int n = clang_getNumArgTypes(ft);
|
||||||
for (int k = 0; k < n && f.unsupported.empty(); k++) {
|
for (int k = 0; k < n && f.unsupported.empty(); k++) {
|
||||||
Type t;
|
Type t;
|
||||||
if (!mapType(clang_getArgType(ft, k), t, why)) f.unsupported = why;
|
if (!mapType(clang_getArgType(ft, k), t, why, Ctx::Signature)) f.unsupported = why;
|
||||||
f.params.push_back(t);
|
f.params.push_back(t);
|
||||||
}
|
}
|
||||||
f.variadic = clang_isFunctionTypeVariadic(ft);
|
f.variadic = clang_isFunctionTypeVariadic(ft);
|
||||||
|
|
@ -180,11 +277,20 @@ CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
|
||||||
if (clang_Cursor_getStorageClass(c) != CX_SC_Extern || out.globals.count(name)) break;
|
if (clang_Cursor_getStorageClass(c) != CX_SC_Extern || out.globals.count(name)) break;
|
||||||
CGlobal g;
|
CGlobal g;
|
||||||
std::string why;
|
std::string why;
|
||||||
if (mapType(clang_getCursorType(c), g.type, why)) out.globals[name] = g;
|
if (mapType(clang_getCursorType(c), g.type, why, Ctx::Data)) out.globals[name] = g;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CXCursor_StructDecl:
|
||||||
|
if (clang_isCursorDefinition(c) && !name.empty() && name.find(' ') == std::string::npos) {
|
||||||
|
std::string usr = str(clang_getCursorUSR(c));
|
||||||
|
if (!recordNames.count(usr)) recordNames[usr] = name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case CXCursor_EnumDecl:
|
case CXCursor_EnumDecl:
|
||||||
clang_visitChildren(c, visit, data);
|
clang_visitChildren(c, [](CXCursor k, CXCursor, CXClientData d) {
|
||||||
|
static_cast<Importer *>(d)->visitTop(k);
|
||||||
|
return CXChildVisit_Continue;
|
||||||
|
}, this);
|
||||||
break;
|
break;
|
||||||
case CXCursor_EnumConstantDecl: {
|
case CXCursor_EnumConstantDecl: {
|
||||||
CConst k;
|
CConst k;
|
||||||
|
|
@ -196,34 +302,39 @@ CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
|
||||||
case CXCursor_MacroDefinition: {
|
case CXCursor_MacroDefinition: {
|
||||||
if (clang_Cursor_isMacroBuiltin(c) || clang_Cursor_isMacroFunctionLike(c)) break;
|
if (clang_Cursor_isMacroBuiltin(c) || clang_Cursor_isMacroFunctionLike(c)) break;
|
||||||
CConst k;
|
CConst k;
|
||||||
if (macroValue(v.tu, c, k)) out.consts.emplace(name, k);
|
if (macroValue(c, k)) out.consts.emplace(name, k);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return CXChildVisit_Continue;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir) {
|
CImports importHeaders(const std::vector<Import> &imports, const std::vector<std::string> &dirs) {
|
||||||
CImports out;
|
CImports out;
|
||||||
if (imports.empty()) return out;
|
bool any = false;
|
||||||
|
for (const Import &im : imports) any |= im.isC;
|
||||||
|
if (!any) return out;
|
||||||
|
|
||||||
std::vector<std::string> args = {"-x", "c", "-std=gnu11", "-I" + dir};
|
std::vector<std::string> args = {"-x", "c", "-std=gnu11"};
|
||||||
|
for (const std::string &d : dirs) args.push_back("-I" + d);
|
||||||
for (const std::string &d : systemIncludeDirs()) args.push_back("-isystem" + d);
|
for (const std::string &d : systemIncludeDirs()) args.push_back("-isystem" + d);
|
||||||
std::vector<const char *> argv;
|
std::vector<const char *> argv;
|
||||||
for (const std::string &a : args) argv.push_back(a.c_str());
|
for (const std::string &a : args) argv.push_back(a.c_str());
|
||||||
|
|
||||||
|
Importer imp(out);
|
||||||
CXIndex index = clang_createIndex(0, 0);
|
CXIndex index = clang_createIndex(0, 0);
|
||||||
for (const Import &im : imports) {
|
for (const Import &im : imports) {
|
||||||
|
if (!im.isC) continue;
|
||||||
std::string src = "#include \"" + im.path + "\"\n";
|
std::string src = "#include \"" + im.path + "\"\n";
|
||||||
CXUnsavedFile file = {"finch_import.c", src.c_str(), (unsigned long)src.size()};
|
CXUnsavedFile file = {"finch_import.c", src.c_str(), (unsigned long)src.size()};
|
||||||
CXTranslationUnit tu = nullptr;
|
CXTranslationUnit tu = nullptr;
|
||||||
CXErrorCode err = clang_parseTranslationUnit2(
|
CXErrorCode err = clang_parseTranslationUnit2(
|
||||||
index, "finch_import.c", argv.data(), (int)argv.size(), &file, 1,
|
index, "finch_import.c", argv.data(), (int)argv.size(), &file, 1,
|
||||||
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies, &tu);
|
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies, &tu);
|
||||||
if (err != CXError_Success || !tu) fail(im.pos.line, im.pos.col, "couldn't read C header '" + im.path + "'");
|
if (err != CXError_Success || !tu) failAt(im.pos.file, im.pos.line, im.pos.col, "couldn't read C header '" + im.path + "'");
|
||||||
|
|
||||||
for (unsigned k = 0; k < clang_getNumDiagnostics(tu); k++) {
|
for (unsigned k = 0; k < clang_getNumDiagnostics(tu); k++) {
|
||||||
CXDiagnostic d = clang_getDiagnostic(tu, k);
|
CXDiagnostic d = clang_getDiagnostic(tu, k);
|
||||||
|
|
@ -231,14 +342,18 @@ CImports importHeaders(const std::vector<Import> &imports, const std::string &di
|
||||||
std::string msg = str(clang_getDiagnosticSpelling(d));
|
std::string msg = str(clang_getDiagnosticSpelling(d));
|
||||||
clang_disposeDiagnostic(d);
|
clang_disposeDiagnostic(d);
|
||||||
if (msg.find("file not found") != std::string::npos)
|
if (msg.find("file not found") != std::string::npos)
|
||||||
fail(im.pos.line, im.pos.col, "can't find the C header '" + im.path + "'");
|
failAt(im.pos.file, im.pos.line, im.pos.col, "can't find the C header '" + im.path + "'");
|
||||||
fail(im.pos.line, im.pos.col, "C header '" + im.path + "' has an error: " + msg);
|
failAt(im.pos.file, im.pos.line, im.pos.col, "C header '" + im.path + "' has an error: " + msg);
|
||||||
}
|
}
|
||||||
clang_disposeDiagnostic(d);
|
clang_disposeDiagnostic(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
Visit v{&out, tu, im.path};
|
imp.tu = tu;
|
||||||
clang_visitChildren(clang_getTranslationUnitCursor(tu), visit, &v);
|
imp.header = im.path;
|
||||||
|
clang_visitChildren(clang_getTranslationUnitCursor(tu), [](CXCursor c, CXCursor, CXClientData d) {
|
||||||
|
static_cast<Importer *>(d)->visitTop(c);
|
||||||
|
return CXChildVisit_Continue;
|
||||||
|
}, &imp);
|
||||||
clang_disposeTranslationUnit(tu);
|
clang_disposeTranslationUnit(tu);
|
||||||
}
|
}
|
||||||
clang_disposeIndex(index);
|
clang_disposeIndex(index);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
#include "ast.h"
|
#include "ast.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// What Finch learned from `import "something.h"`.
|
// What Finch learned from `import "something.h"`.
|
||||||
|
// C structs by value appear in types as Named{name, module "C"}.
|
||||||
|
|
||||||
struct CFunc {
|
struct CFunc {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
@ -27,11 +29,28 @@ struct CGlobal { // extern variables like stdout
|
||||||
Type type;
|
Type type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CField {
|
||||||
|
std::string name;
|
||||||
|
Type type;
|
||||||
|
long long offset = 0; // bytes
|
||||||
|
long long size = 0, align = 1;
|
||||||
|
bool hidden = false; // unions, bitfields, unmappable types: kept for layout only
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CStruct {
|
||||||
|
std::string name;
|
||||||
|
std::vector<CField> fields;
|
||||||
|
long long size = 0, align = 1;
|
||||||
|
std::string unsupported; // why it can't be used by value (pointers to it still work)
|
||||||
|
bool hasHidden = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct CImports {
|
struct CImports {
|
||||||
std::unordered_map<std::string, CFunc> fns;
|
std::unordered_map<std::string, CFunc> fns;
|
||||||
std::unordered_map<std::string, CConst> consts;
|
std::unordered_map<std::string, CConst> consts;
|
||||||
std::unordered_map<std::string, CGlobal> globals;
|
std::unordered_map<std::string, CGlobal> globals;
|
||||||
|
std::map<std::string, CStruct> structs;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `dir` is the folder of the .fn file, searched first for "local.h" headers.
|
// `dirs`: folders searched first for "local.h" headers (the .fn files' folders).
|
||||||
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir);
|
CImports importHeaders(const std::vector<Import> &imports, const std::vector<std::string> &dirs);
|
||||||
|
|
|
||||||
1239
src/codegen.cpp
1239
src/codegen.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
// All files of the program (the main file first, then its modules) become one LLVM module.
|
||||||
// `tm` describes the machine we compile for (sizes, alignment), which shapes the IR.
|
// `tm` describes the machine we compile for (sizes, alignment), which shapes the IR.
|
||||||
std::unique_ptr<llvm::Module> generate(const Program &prog, const CImports &c, llvm::LLVMContext &ctx,
|
std::unique_ptr<llvm::Module> generate(const std::vector<Program> &progs, const CImports &c, llvm::LLVMContext &ctx,
|
||||||
llvm::TargetMachine &tm);
|
llvm::TargetMachine &tm, bool debug);
|
||||||
|
|
|
||||||
703
src/codegen_builtins.cpp
Normal file
703
src/codegen_builtins.cpp
Normal file
|
|
@ -0,0 +1,703 @@
|
||||||
|
// Code generation, part 3: calls, built-in functions, conversions and methods.
|
||||||
|
|
||||||
|
#include "codegen_impl.h"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
|
||||||
|
namespace finch {
|
||||||
|
|
||||||
|
bool Codegen::isBuiltin(const std::string &n) {
|
||||||
|
static const std::set<std::string> names = {"print", "addr", "input", "new", "free", "read_file", "write_file",
|
||||||
|
"file_exists", "exit"};
|
||||||
|
FType t;
|
||||||
|
return names.count(n) || typeFromName(n, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help people who forgot an import.
|
||||||
|
std::string Codegen::headerHint(const std::string &name) {
|
||||||
|
static const std::unordered_map<std::string, const char *> known = {
|
||||||
|
{"printf", "stdio.h"}, {"puts", "stdio.h"}, {"putchar", "stdio.h"}, {"scanf", "stdio.h"},
|
||||||
|
{"fopen", "stdio.h"}, {"getchar", "stdio.h"}, {"sqrt", "math.h"}, {"pow", "math.h"},
|
||||||
|
{"sin", "math.h"}, {"cos", "math.h"}, {"floor", "math.h"}, {"fabs", "math.h"},
|
||||||
|
{"malloc", "stdlib.h"}, {"rand", "stdlib.h"}, {"srand", "stdlib.h"}, {"abs", "stdlib.h"},
|
||||||
|
{"atoi", "stdlib.h"}, {"strlen", "string.h"}, {"strcpy", "string.h"}, {"memcpy", "string.h"},
|
||||||
|
{"time", "time.h"}, {"sleep", "unistd.h"}, {"usleep", "unistd.h"},
|
||||||
|
};
|
||||||
|
auto it = known.find(name);
|
||||||
|
return it == known.end() ? "" : std::string(" (it's from C: add import \"") + it->second + "\" at the top)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::checkArgs(const std::vector<ExprPtr> &args, const std::vector<std::string> &names, size_t want, Pos p,
|
||||||
|
const std::string &name, bool variadic) {
|
||||||
|
for (size_t i = 0; i < names.size(); i++)
|
||||||
|
if (!names[i].empty())
|
||||||
|
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "named arguments (" + names[i] + ": ...) are only for creating structs");
|
||||||
|
if (args.size() == want || (variadic && args.size() > want)) return;
|
||||||
|
failAt(p.file, p.line, p.col, "'" + name + "' takes " + (variadic ? "at least " : "") + std::to_string(want) +
|
||||||
|
" argument(s), but got " + std::to_string(args.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Function *Codegen::libc(const char *name, llvm::Type *ret, std::vector<llvm::Type *> params, bool vararg) {
|
||||||
|
FunctionCallee c = mod->getOrInsertFunction(name, FunctionType::get(ret, params, vararg));
|
||||||
|
return cast<Function>(c.getCallee());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The runtime (runtime/finch_rt.c), declared on first use.
|
||||||
|
Function *Codegen::rt(const std::string &name) {
|
||||||
|
if (Function *f = mod->getFunction(name)) return f;
|
||||||
|
llvm::Type *P = b.getPtrTy(), *I = b.getInt64Ty(), *I32 = b.getInt32Ty(), *D = b.getDoubleTy(), *V = b.getVoidTy();
|
||||||
|
struct Sig { llvm::Type *ret; std::vector<llvm::Type *> params; };
|
||||||
|
std::map<std::string, Sig> sigs = {
|
||||||
|
{"finch_panic", {V, {P, I, P}}},
|
||||||
|
{"finch_panic_index", {V, {P, I, I, I}}},
|
||||||
|
{"finch_alloc", {P, {I}}},
|
||||||
|
{"finch_zalloc", {P, {I}}},
|
||||||
|
{"finch_str_copy", {V, {P, P}}},
|
||||||
|
{"finch_str_own", {V, {P}}},
|
||||||
|
{"finch_str_drop", {V, {P}}},
|
||||||
|
{"finch_str_from_c", {V, {P, P}}},
|
||||||
|
{"finch_str_concat", {V, {P, P, P}}},
|
||||||
|
{"finch_str_eq", {I32, {P, P}}},
|
||||||
|
{"finch_str_cmp", {I, {P, P}}},
|
||||||
|
{"finch_str_from_int", {V, {P, I}}},
|
||||||
|
{"finch_str_from_uint", {V, {P, I}}},
|
||||||
|
{"finch_str_from_float", {V, {P, D}}},
|
||||||
|
{"finch_str_from_char", {V, {P, b.getInt8Ty()}}},
|
||||||
|
{"finch_str_from_bool", {V, {P, I32}}},
|
||||||
|
{"finch_str_to_int", {I, {P, P, I}}},
|
||||||
|
{"finch_str_to_float", {D, {P, P, I}}},
|
||||||
|
{"finch_str_sub", {V, {P, P, I, I, P, I}}},
|
||||||
|
{"finch_str_find", {I, {P, P}}},
|
||||||
|
{"finch_str_starts", {I32, {P, P}}},
|
||||||
|
{"finch_str_ends", {I32, {P, P}}},
|
||||||
|
{"finch_str_trim", {V, {P, P}}},
|
||||||
|
{"finch_str_upper", {V, {P, P}}},
|
||||||
|
{"finch_str_lower", {V, {P, P}}},
|
||||||
|
{"finch_str_replace", {V, {P, P, P, P}}},
|
||||||
|
{"finch_str_split", {V, {P, P, P}}},
|
||||||
|
{"finch_str_join", {V, {P, P, P}}},
|
||||||
|
{"finch_str_repeat", {V, {P, P, I}}},
|
||||||
|
{"finch_str_from_bytes", {V, {P, P}}},
|
||||||
|
{"finch_arr_reserve", {V, {P, I, I}}},
|
||||||
|
{"finch_arr_make", {V, {P, I, I}}},
|
||||||
|
{"finch_arr_resize", {V, {P, I, I}}},
|
||||||
|
{"finch_arr_clone_raw", {V, {P, P, I}}},
|
||||||
|
{"finch_arr_free", {V, {P}}},
|
||||||
|
{"finch_arr_insert_gap", {V, {P, I, I, P, I}}},
|
||||||
|
{"finch_arr_remove_gap", {V, {P, I, I}}},
|
||||||
|
{"finch_bytes_from_str", {V, {P, P}}},
|
||||||
|
{"finch_args", {V, {P, I32, P}}},
|
||||||
|
{"finch_input", {V, {P, P}}},
|
||||||
|
{"finch_file_exists", {I32, {P}}},
|
||||||
|
{"finch_read_file", {V, {P, P, P, I}}},
|
||||||
|
{"finch_write_file", {I32, {P, P}}},
|
||||||
|
};
|
||||||
|
auto it = sigs.find(name);
|
||||||
|
if (it == sigs.end()) {
|
||||||
|
std::fprintf(stderr, "internal compiler error: unknown runtime function %s\n", name.c_str());
|
||||||
|
std::exit(2);
|
||||||
|
}
|
||||||
|
Function *f = Function::Create(FunctionType::get(it->second.ret, it->second.params, false), Function::ExternalLinkage,
|
||||||
|
name, mod.get());
|
||||||
|
if (name == "finch_panic" || name == "finch_panic_index") {
|
||||||
|
f->addFnAttr(Attribute::NoReturn);
|
||||||
|
f->addFnAttr(Attribute::Cold);
|
||||||
|
}
|
||||||
|
if (name == "finch_str_from_char") f->addParamAttr(1, Attribute::SExt);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- runtime errors ----------
|
||||||
|
|
||||||
|
Value *Codegen::fileName(Pos p) {
|
||||||
|
std::string key = "file." + std::to_string(p.file);
|
||||||
|
if (GlobalVariable *g = mod->getNamedGlobal("finch." + key)) return g;
|
||||||
|
GlobalVariable *g = b.CreateGlobalString(g_files[p.file].path, "finch." + key);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::panicIf(Value *cond, const std::string &msg, Pos p) {
|
||||||
|
BasicBlock *bad = newBlock("panic");
|
||||||
|
BasicBlock *ok = newBlock("ok");
|
||||||
|
b.CreateCondBr(cond, bad, ok);
|
||||||
|
b.SetInsertPoint(bad);
|
||||||
|
b.CreateCall(rt("finch_panic"), {fileName(p), b.getInt64(p.line), b.CreateGlobalString(msg, "panic.msg")});
|
||||||
|
b.CreateUnreachable();
|
||||||
|
b.SetInsertPoint(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::boundsCheck(Value *i, Value *len, Pos p) {
|
||||||
|
BasicBlock *bad = newBlock("out.of.range");
|
||||||
|
BasicBlock *ok = newBlock("in.range");
|
||||||
|
b.CreateCondBr(b.CreateICmpUGE(i, len), bad, ok); // unsigned: catches negative indexes too
|
||||||
|
b.SetInsertPoint(bad);
|
||||||
|
b.CreateCall(rt("finch_panic_index"), {fileName(p), b.getInt64(p.line), i, len});
|
||||||
|
b.CreateUnreachable();
|
||||||
|
b.SetInsertPoint(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dividing by zero is undefined in C; in Finch it is a clear error.
|
||||||
|
void Codegen::checkDivisor(const Value_ &l, const Value_ &r, Pos p) {
|
||||||
|
auto *c = dyn_cast<ConstantInt>(r.v);
|
||||||
|
if (c && c->isZero()) failAt(p.file, p.line, p.col, "division by zero");
|
||||||
|
if (!c) panicIf(b.CreateICmpEQ(r.v, ConstantInt::get(r.v->getType(), 0)), "division by zero", p);
|
||||||
|
// the smallest signed number divided by -1 doesn't fit anywhere
|
||||||
|
if (l.type.isSigned() && (!c || c->isMinusOne())) {
|
||||||
|
Value *min = ConstantInt::get(l.v->getType(), APInt::getSignedMinValue(l.type.bits()));
|
||||||
|
Value *minusOne = ConstantInt::get(r.v->getType(), -1, true);
|
||||||
|
panicIf(b.CreateAnd(b.CreateICmpEQ(l.v, min), b.CreateICmpEQ(r.v, minusOne)),
|
||||||
|
"division overflows (the smallest number divided by -1)", p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::cGlobal(const std::string &name) {
|
||||||
|
if (GlobalVariable *gv = mod->getNamedGlobal(name)) return gv;
|
||||||
|
FType t = resolve(cimports.globals.at(name).type, Pos{});
|
||||||
|
return new GlobalVariable(*mod, ty(t), false, GlobalValue::ExternalLinkage, nullptr, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- calls ----------
|
||||||
|
|
||||||
|
Value_ Codegen::call(const CallExpr &c) {
|
||||||
|
const std::string &n = c.callee;
|
||||||
|
Pos p = c.pos;
|
||||||
|
auto one = [&](const char *what) -> const Expr & {
|
||||||
|
checkArgs(c.args, c.argNames, 1, p, n);
|
||||||
|
(void)what;
|
||||||
|
return *c.args[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (n == "print") {
|
||||||
|
checkArgs(c.args, c.argNames, 0, p, n, true);
|
||||||
|
return print(c.args, p);
|
||||||
|
}
|
||||||
|
if (n == "addr") {
|
||||||
|
checkArgs(c.args, c.argNames, 1, p, n);
|
||||||
|
return addrOf(c.args, p);
|
||||||
|
}
|
||||||
|
if (n == "input") {
|
||||||
|
if (c.args.size() > 1) checkArgs(c.args, c.argNames, 1, p, n);
|
||||||
|
Value_ prompt = c.args.empty() ? Value_{strConst(""), FType::Str, false, true}
|
||||||
|
: coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the question");
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
b.CreateCall(rt("finch_input"), {out, tmp(prompt.v)});
|
||||||
|
release(prompt);
|
||||||
|
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||||
|
}
|
||||||
|
if (n == "new") {
|
||||||
|
const Expr &a = one("value");
|
||||||
|
Value_ v = expr(a);
|
||||||
|
if (v.type.kind == FType::Void || v.type.kind == FType::Null)
|
||||||
|
failAt(a.pos.file, a.pos.line, a.pos.col, "new(...) needs a value to put in memory, like new(Point(x: 1, y: 2))");
|
||||||
|
Value *mem = b.CreateCall(rt("finch_alloc"), {elemSize(v.type)});
|
||||||
|
b.CreateStore(own(v), mem);
|
||||||
|
return {mem, FType::ptrTo(v.type)};
|
||||||
|
}
|
||||||
|
if (n == "free") {
|
||||||
|
const Expr &a = one("pointer");
|
||||||
|
Value_ v = expr(a);
|
||||||
|
if (!v.type.isPtr())
|
||||||
|
failAt(a.pos.file, a.pos.line, a.pos.col, "free(...) is only for pointers from new(...) or C; arrays, str and structs are freed by Finch at the end of their block");
|
||||||
|
if (v.type.elem && owning(*v.type.elem)) {
|
||||||
|
BasicBlock *drop = newBlock("free.drop"), *done = newBlock("free.done");
|
||||||
|
b.CreateCondBr(b.CreateIsNull(v.v), done, drop);
|
||||||
|
b.SetInsertPoint(drop);
|
||||||
|
dropAt(v.v, *v.type.elem);
|
||||||
|
b.CreateBr(done);
|
||||||
|
b.SetInsertPoint(done);
|
||||||
|
}
|
||||||
|
b.CreateCall(libc("free", b.getVoidTy(), {b.getPtrTy()}), {v.v});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "exit") {
|
||||||
|
Value *code = coerce(expr(one("code")), FType::I32, c.args[0]->pos, "the exit code").v;
|
||||||
|
b.CreateCall(libc("fflush", b.getInt32Ty(), {b.getPtrTy()}), {ConstantPointerNull::get(b.getPtrTy())});
|
||||||
|
b.CreateCall(libc("exit", b.getVoidTy(), {b.getInt32Ty()}), {code});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "read_file" || n == "file_exists") {
|
||||||
|
Value_ path = coerce(expr(one("path")), FType::Str, c.args[0]->pos, "the file name");
|
||||||
|
Value *pp = tmp(path.v);
|
||||||
|
Value_ res;
|
||||||
|
if (n == "file_exists") {
|
||||||
|
res = {b.CreateICmpNE(b.CreateCall(rt("finch_file_exists"), {pp}), b.getInt32(0)), FType::Bool};
|
||||||
|
} else {
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
b.CreateCall(rt("finch_read_file"), {out, pp, fileName(p), b.getInt64(p.line)});
|
||||||
|
res = {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||||
|
}
|
||||||
|
release(path);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (n == "write_file") {
|
||||||
|
checkArgs(c.args, c.argNames, 2, p, n);
|
||||||
|
Value_ path = coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the file name");
|
||||||
|
Value_ text = coerce(expr(*c.args[1]), FType::Str, c.args[1]->pos, "the text");
|
||||||
|
Value *ok = b.CreateCall(rt("finch_write_file"), {tmp(path.v), tmp(text.v)});
|
||||||
|
release(path);
|
||||||
|
release(text);
|
||||||
|
return {b.CreateICmpNE(ok, b.getInt32(0)), FType::Bool};
|
||||||
|
}
|
||||||
|
FType target;
|
||||||
|
if (typeFromName(n, target)) {
|
||||||
|
checkArgs(c.args, c.argNames, 1, p, n);
|
||||||
|
return convert(target, c.args, p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleScope &m = modules[curModule];
|
||||||
|
if (auto it = m.fns.find(n); it != m.fns.end()) return callFinch(it->second, c.args, c.argNames, p, n);
|
||||||
|
if (StructInfo *s = findStruct("", n, false)) return construct(s, c.args, c.argNames, p);
|
||||||
|
if (auto cf = cimports.fns.find(n); cf != cimports.fns.end()) {
|
||||||
|
checkArgs(c.args, c.argNames, cf->second.params.size(), p, n, cf->second.variadic);
|
||||||
|
return callC(cf->second, c.args, p);
|
||||||
|
}
|
||||||
|
if (lookup(n)) failAt(p.file, p.line, p.col, "'" + n + "' is a variable, not a function");
|
||||||
|
for (auto &[mname, ms] : modules)
|
||||||
|
if (ms.fns.count(n) && mname != curModule && !mname.empty())
|
||||||
|
failAt(p.file, p.line, p.col, "there is no function named '" + n + "' here; it is in the module " + mname +
|
||||||
|
" (write " + mname + "." + n + "(...)" + (m.imports.count(mname) ? "" : " and add: import " + mname) + ")");
|
||||||
|
failAt(p.file, p.line, p.col, "there is no function named '" + n + "'" + headerHint(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
|
||||||
|
const std::string &shown) {
|
||||||
|
const FnDecl &d = *fn.decl;
|
||||||
|
if (fn.module.empty() && d.name == "main") failAt(p.file, p.line, p.col, "main can't be called, it is where the program starts");
|
||||||
|
checkArgs(args, names, fn.params.size(), p, shown);
|
||||||
|
std::vector<Value *> vals;
|
||||||
|
std::vector<Value_> fresh;
|
||||||
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
|
Value_ v = coerce(exprWant(*args[i], fn.params[i]), fn.params[i], args[i]->pos, "argument '" + d.params[i].name + "'");
|
||||||
|
vals.push_back(v.v); // owning values are lent: the function never drops them
|
||||||
|
if (v.fresh) fresh.push_back(v);
|
||||||
|
}
|
||||||
|
setLoc(p);
|
||||||
|
Value *r = b.CreateCall(fn.llvm, vals);
|
||||||
|
for (auto &v : fresh) release(v);
|
||||||
|
if (fn.ret.kind == FType::Void) return {nullptr, FType::Void};
|
||||||
|
return {r, fn.ret, false, owning(fn.ret)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::construct(StructInfo *s, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p) {
|
||||||
|
resolveStruct(s);
|
||||||
|
FType t(FType::Struct);
|
||||||
|
t.info = s;
|
||||||
|
t.name = s->name;
|
||||||
|
t.module = s->module == "C" ? "" : s->module;
|
||||||
|
std::vector<const StructInfo::F *> fields;
|
||||||
|
for (auto &f : s->fields) fields.push_back(&f);
|
||||||
|
|
||||||
|
bool named = !names.empty() && !names[0].empty();
|
||||||
|
std::vector<const Expr *> given(fields.size(), nullptr);
|
||||||
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
|
Pos ap = args[i]->pos;
|
||||||
|
if (names[i].empty() == named)
|
||||||
|
failAt(ap.file, ap.line, ap.col, "give either all fields by name (x: 1, y: 2) or none (1, 2), not a mix");
|
||||||
|
if (!named) {
|
||||||
|
if (args.size() != fields.size())
|
||||||
|
failAt(p.file, p.line, p.col, s->name + " has " + std::to_string(fields.size()) + " fields, but got " +
|
||||||
|
std::to_string(args.size()) + " values (or name them: " + s->name + "(field: value))");
|
||||||
|
given[i] = args[i].get();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t k = 0;
|
||||||
|
while (k < fields.size() && fields[k]->name != names[i]) k++;
|
||||||
|
if (k == fields.size()) failAt(ap.file, ap.line, ap.col, s->name + " has no field '" + names[i] + "'");
|
||||||
|
if (given[k]) failAt(ap.file, ap.line, ap.col, "the field '" + names[i] + "' is given twice");
|
||||||
|
given[k] = args[i].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *v = zero(t);
|
||||||
|
for (size_t k = 0; k < fields.size(); k++) {
|
||||||
|
const StructInfo::F &f = *fields[k];
|
||||||
|
Value_ fv;
|
||||||
|
if (given[k]) {
|
||||||
|
fv = coerce(exprWant(*given[k], f.type), f.type, given[k]->pos, "the field '" + f.name + "'");
|
||||||
|
} else if (f.init) {
|
||||||
|
// a default value is evaluated where the struct is defined, without the caller's variables
|
||||||
|
std::vector<Scope> saved;
|
||||||
|
std::swap(saved, scopes);
|
||||||
|
scopes.reserve(256);
|
||||||
|
pushScope();
|
||||||
|
std::string savedModule = curModule;
|
||||||
|
curModule = s->module;
|
||||||
|
fv = coerce(exprWant(*f.init, f.type), f.type, f.init->pos, "the default of '" + f.name + "'");
|
||||||
|
curModule = savedModule;
|
||||||
|
scopes.pop_back();
|
||||||
|
std::swap(saved, scopes);
|
||||||
|
} else {
|
||||||
|
continue; // zero
|
||||||
|
}
|
||||||
|
v = b.CreateInsertValue(v, own(fv), f.llvmIndex);
|
||||||
|
}
|
||||||
|
return {v, t, false, owning(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p) {
|
||||||
|
if (!f.unsupported.empty())
|
||||||
|
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' can't be used from Finch yet: it " + f.unsupported);
|
||||||
|
std::vector<Value_> vals;
|
||||||
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
|
if (i < f.params.size()) {
|
||||||
|
FType pt = resolve(f.params[i], p);
|
||||||
|
vals.push_back(coerce(exprWant(*args[i], pt), pt, args[i]->pos, "argument " + std::to_string(i + 1) + " of " + f.name));
|
||||||
|
} else {
|
||||||
|
Value_ v = expr(*args[i]);
|
||||||
|
if (v.type.kind == FType::Void) failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "this gives back nothing");
|
||||||
|
if (v.type.kind == FType::Struct || v.type.kind == FType::Array)
|
||||||
|
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "structs and arrays can't be passed to the '...' part of a C function");
|
||||||
|
vals.push_back(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoc(p);
|
||||||
|
Value_ r = emitCCall(f, declareC(f), vals, p);
|
||||||
|
for (auto &v : vals) release(v);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- built-ins ----------
|
||||||
|
|
||||||
|
// print(a, b, c) prints the values separated by spaces, then a newline.
|
||||||
|
Value_ Codegen::print(const std::vector<ExprPtr> &args, Pos) {
|
||||||
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
|
Value_ v = expr(*args[i]);
|
||||||
|
if (v.type.kind == FType::Void)
|
||||||
|
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "this gives back nothing, so there is nothing to print");
|
||||||
|
if (i) printf_(" ", {});
|
||||||
|
emitPrint(v.v, v.type, false);
|
||||||
|
release(v);
|
||||||
|
}
|
||||||
|
printf_("\n", {});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
|
||||||
|
// addr(x) gives a pointer to x; addr(function) gives a C-callable function pointer.
|
||||||
|
Value_ Codegen::addrOf(const std::vector<ExprPtr> &args, Pos p) {
|
||||||
|
const Expr &e = *args[0];
|
||||||
|
if (e.kind == ExprKind::Var) {
|
||||||
|
const std::string &n = static_cast<const VarExpr &>(e).name;
|
||||||
|
Var *v = lookup(n);
|
||||||
|
if (!v) {
|
||||||
|
auto &fns = modules[curModule].fns;
|
||||||
|
if (auto it = fns.find(n); it != fns.end()) return {cThunk(it->second, p), FType::Ptr};
|
||||||
|
}
|
||||||
|
if (v && v->readonly) failAt(e.pos.file, e.pos.line, e.pos.col, "can't take the address of a loop variable");
|
||||||
|
}
|
||||||
|
if (e.kind != ExprKind::Var && e.kind != ExprKind::Member && e.kind != ExprKind::Index)
|
||||||
|
failAt(e.pos.file, e.pos.line, e.pos.col, "addr(...) needs a variable, a field or an element, like addr(x)");
|
||||||
|
Place pl = place(e, true);
|
||||||
|
return {pl.addr, FType::ptrTo(pl.type)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit conversions: int(x), u8(x), float(x), char(x), bool(x), str(x), ptr(x) ...
|
||||||
|
Value_ Codegen::convert(const FType &to, const std::vector<ExprPtr> &args, Pos, const std::string &name) {
|
||||||
|
Value_ v = expr(*args[0]);
|
||||||
|
const FType &from = v.type;
|
||||||
|
Pos p = args[0]->pos;
|
||||||
|
if (to.kind == FType::Str) {
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
if (from.kind == FType::Str) return {copyValue(v.v, FType::Str), FType::Str, false, true};
|
||||||
|
if (from.isPtr()) return strFromC(v.v);
|
||||||
|
if (from.isSigned()) b.CreateCall(rt("finch_str_from_int"), {out, b.CreateSExt(v.v, b.getInt64Ty())});
|
||||||
|
else if (from.isUnsigned()) b.CreateCall(rt("finch_str_from_uint"), {out, b.CreateZExt(v.v, b.getInt64Ty())});
|
||||||
|
else if (from.isFloat()) b.CreateCall(rt("finch_str_from_float"), {out, b.CreateFPExt(v.v, b.getDoubleTy())});
|
||||||
|
else if (from.kind == FType::Char) b.CreateCall(rt("finch_str_from_char"), {out, v.v});
|
||||||
|
else if (from.kind == FType::Bool) b.CreateCall(rt("finch_str_from_bool"), {out, b.CreateZExt(v.v, b.getInt32Ty())});
|
||||||
|
else if (from.kind == FType::Array && (from.elem->kind == FType::U8 || from.elem->kind == FType::Char)) {
|
||||||
|
b.CreateCall(rt("finch_str_from_bytes"), {out, tmp(v.v)});
|
||||||
|
release(v);
|
||||||
|
} else {
|
||||||
|
failAt(p.file, p.line, p.col, "can't turn " + from.show() + " into str");
|
||||||
|
}
|
||||||
|
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||||
|
}
|
||||||
|
if (from == to) return v;
|
||||||
|
if (from.kind == FType::Str && (to.isInt() || to.isFloat())) {
|
||||||
|
Value *sp = tmp(v.v);
|
||||||
|
Value_ r;
|
||||||
|
if (to.isInt()) r = {intCast(b.CreateCall(rt("finch_str_to_int"), {sp, fileName(p), b.getInt64(p.line)}), FType::I64, to), to};
|
||||||
|
else r = {b.CreateFPCast(b.CreateCall(rt("finch_str_to_float"), {sp, fileName(p), b.getInt64(p.line)}), ty(to)), to};
|
||||||
|
release(v);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (to.isInt() || to.kind == FType::Char) {
|
||||||
|
if (from.isInt() || from.kind == FType::Char) return {b.CreateIntCast(v.v, ty(to), from.isSigned()), to};
|
||||||
|
if (from.kind == FType::Bool) return {b.CreateZExt(v.v, ty(to)), to};
|
||||||
|
if (from.isFloat() && to.isInt()) return {to.isSigned() ? b.CreateFPToSI(v.v, ty(to)) : b.CreateFPToUI(v.v, ty(to)), to};
|
||||||
|
}
|
||||||
|
if (to.isFloat()) {
|
||||||
|
if (from.isInt()) return {intToFloat(v.v, from, to), to};
|
||||||
|
if (from.isFloat()) return {b.CreateFPCast(v.v, ty(to)), to};
|
||||||
|
}
|
||||||
|
if (to.kind == FType::Bool && from.isInt()) return {b.CreateICmpNE(v.v, ConstantInt::get(ty(from), 0)), to};
|
||||||
|
if (to.isPtr() && (from.isPtr() || from.kind == FType::Null)) return {v.v, to};
|
||||||
|
if (to.isPtr() && from.kind == FType::Str) failAt(p.file, p.line, p.col, "use .ptr to get a str's address (it is only valid while the str lives)");
|
||||||
|
failAt(p.file, p.line, p.col, "can't turn " + from.show() + " into " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- methods ----------
|
||||||
|
|
||||||
|
Value_ Codegen::method(const MethodExpr &m) {
|
||||||
|
Pos p = m.pos;
|
||||||
|
// math.sqrt(x), math.Vec(1, 2)
|
||||||
|
if (m.obj->kind == ExprKind::Var) {
|
||||||
|
const std::string &mn = static_cast<const VarExpr &>(*m.obj).name;
|
||||||
|
if (!lookup(mn) && modules[curModule].imports.count(mn)) {
|
||||||
|
auto mi = modules.find(mn);
|
||||||
|
if (mi == modules.end()) failAt(p.file, p.line, p.col, "the module '" + mn + "' was not found");
|
||||||
|
if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end())
|
||||||
|
return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name);
|
||||||
|
if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end())
|
||||||
|
return construct(it->second, m.args, m.argNames, p);
|
||||||
|
failAt(p.file, p.line, p.col, "the module '" + mn + "' has no function or struct named '" + m.name + "'");
|
||||||
|
}
|
||||||
|
if (!lookup(mn) && !cimports.globals.count(mn) && !cimports.consts.count(mn))
|
||||||
|
failAt(m.obj->pos.file, m.obj->pos.line, m.obj->pos.col,
|
||||||
|
"there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)");
|
||||||
|
}
|
||||||
|
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
|
||||||
|
LRef r = ref(*m.obj, changing.count(m.name) > 0);
|
||||||
|
FType t = r.isPlace ? r.pl.type : r.val.type;
|
||||||
|
if (t.kind == FType::Array) {
|
||||||
|
Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : r.val;
|
||||||
|
if (!r.isPlace) {
|
||||||
|
if (changing.count(m.name)) failAt(p.file, p.line, p.col, m.name + "() would change a temporary array; store it in a variable first");
|
||||||
|
recv = {tmp(r.val.v), t, false, r.val.fresh};
|
||||||
|
}
|
||||||
|
Value_ out = arrayMethod(m, &recv);
|
||||||
|
if (!r.isPlace && r.val.fresh) dropAt(recv.v, t);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (t.kind == FType::Str) {
|
||||||
|
Value_ sv = r.isPlace ? Value_{b.CreateLoad(ty(t), r.pl.addr), t} : r.val;
|
||||||
|
Value *addr = tmp(sv.v); // the runtime takes strings by address
|
||||||
|
auto argStr = [&](size_t i, const char *what) {
|
||||||
|
return coerce(expr(*m.args[i]), FType::Str, m.args[i]->pos, what);
|
||||||
|
};
|
||||||
|
auto argInt = [&](size_t i, const char *what) {
|
||||||
|
return coerce(expr(*m.args[i]), FType::I64, m.args[i]->pos, what).v;
|
||||||
|
};
|
||||||
|
auto need = [&](size_t n) { checkArgs(m.args, m.argNames, n, p, m.name); };
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
Value_ res;
|
||||||
|
const std::string &n = m.name;
|
||||||
|
auto strRes = [&]() { return Value_{b.CreateLoad(ty(FType::Str), out), FType::Str, false, true}; };
|
||||||
|
if (n == "sub") {
|
||||||
|
need(2);
|
||||||
|
Value *a = argInt(0, "the start"), *e = argInt(1, "the end");
|
||||||
|
b.CreateCall(rt("finch_str_sub"), {out, addr, a, e, fileName(p), b.getInt64(p.line)});
|
||||||
|
res = strRes();
|
||||||
|
} else if (n == "find" || n == "contains" || n == "starts_with" || n == "ends_with") {
|
||||||
|
need(1);
|
||||||
|
Value_ t2 = argStr(0, "the text to look for");
|
||||||
|
Value *tp = tmp(t2.v);
|
||||||
|
if (n == "find") res = {b.CreateCall(rt("finch_str_find"), {addr, tp}), FType::I64};
|
||||||
|
else if (n == "contains") res = {b.CreateICmpSGE(b.CreateCall(rt("finch_str_find"), {addr, tp}), b.getInt64(0)), FType::Bool};
|
||||||
|
else res = {b.CreateICmpNE(b.CreateCall(rt(n == "starts_with" ? "finch_str_starts" : "finch_str_ends"), {addr, tp}), b.getInt32(0)), FType::Bool};
|
||||||
|
release(t2);
|
||||||
|
} else if (n == "trim" || n == "upper" || n == "lower") {
|
||||||
|
need(0);
|
||||||
|
b.CreateCall(rt("finch_str_" + n), {out, addr});
|
||||||
|
res = strRes();
|
||||||
|
} else if (n == "replace") {
|
||||||
|
need(2);
|
||||||
|
Value_ a = argStr(0, "the text to replace"), c = argStr(1, "the new text");
|
||||||
|
b.CreateCall(rt("finch_str_replace"), {out, addr, tmp(a.v), tmp(c.v)});
|
||||||
|
release(a);
|
||||||
|
release(c);
|
||||||
|
res = strRes();
|
||||||
|
} else if (n == "split") {
|
||||||
|
need(1);
|
||||||
|
Value_ sep = argStr(0, "the separator");
|
||||||
|
FType at = FType::arrayOf(FType::Str);
|
||||||
|
Value *ao = tmpOf(ty(at));
|
||||||
|
b.CreateCall(rt("finch_str_split"), {ao, addr, tmp(sep.v)});
|
||||||
|
release(sep);
|
||||||
|
res = {b.CreateLoad(ty(at), ao), at, false, true};
|
||||||
|
} else if (n == "repeat") {
|
||||||
|
need(1);
|
||||||
|
b.CreateCall(rt("finch_str_repeat"), {out, addr, argInt(0, "the count")});
|
||||||
|
res = strRes();
|
||||||
|
} else if (n == "bytes") {
|
||||||
|
need(0);
|
||||||
|
FType at = FType::arrayOf(FType::U8);
|
||||||
|
Value *ao = tmpOf(ty(at));
|
||||||
|
b.CreateCall(rt("finch_bytes_from_str"), {ao, addr});
|
||||||
|
res = {b.CreateLoad(ty(at), ao), at, false, true};
|
||||||
|
} else {
|
||||||
|
failAt(p.file, p.line, p.col, "a str has no method '" + n + "' (it has: sub, find, contains, starts_with, ends_with, split, trim, upper, lower, replace, repeat, bytes)");
|
||||||
|
}
|
||||||
|
if (!r.isPlace) release(sv);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (t.kind == FType::Struct)
|
||||||
|
failAt(p.file, p.line, p.col, "structs don't have methods; write a function and call it: " + m.name + "(value, ...)");
|
||||||
|
failAt(p.file, p.line, p.col, t.show() + " has no methods");
|
||||||
|
}
|
||||||
|
|
||||||
|
// An element compare for find/contains/sort: -1, 0, 1 (only numbers, chars, bools and str)
|
||||||
|
static bool comparable(const FType &t) {
|
||||||
|
return t.isNumber() || t.kind == FType::Char || t.kind == FType::Bool || t.kind == FType::Str;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::arrayMethod(const MethodExpr &m, Value_ *recv) {
|
||||||
|
Pos p = m.pos;
|
||||||
|
const std::string &n = m.name;
|
||||||
|
FType at = recv->type, et = *at.elem;
|
||||||
|
llvm::Type *AT = ty(at), *ET = ty(et);
|
||||||
|
Value *a = recv->v; // the array's address
|
||||||
|
auto need = [&](size_t k) { checkArgs(m.args, m.argNames, k, p, n); };
|
||||||
|
auto lenAddr = [&]() { return b.CreateStructGEP(AT, a, 1); };
|
||||||
|
auto len = [&]() { return b.CreateLoad(b.getInt64Ty(), lenAddr()); };
|
||||||
|
auto data = [&]() { return b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(AT, a, 0)); };
|
||||||
|
auto elemValue = [&](size_t i) { return own(coerce(exprWant(*m.args[i], et), et, m.args[i]->pos, "the element")); };
|
||||||
|
|
||||||
|
if (n == "push") {
|
||||||
|
need(1);
|
||||||
|
Value *v = elemValue(0);
|
||||||
|
Value *l = len();
|
||||||
|
b.CreateCall(rt("finch_arr_reserve"), {a, b.CreateAdd(l, b.getInt64(1)), elemSize(et)});
|
||||||
|
b.CreateStore(v, b.CreateInBoundsGEP(ET, data(), l));
|
||||||
|
b.CreateStore(b.CreateAdd(l, b.getInt64(1)), lenAddr());
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "pop") {
|
||||||
|
need(0);
|
||||||
|
Value *l = len();
|
||||||
|
panicIf(b.CreateICmpEQ(l, b.getInt64(0)), "pop() on an empty array", p);
|
||||||
|
Value *last = b.CreateSub(l, b.getInt64(1));
|
||||||
|
Value *v = b.CreateLoad(ET, b.CreateInBoundsGEP(ET, data(), last));
|
||||||
|
b.CreateStore(last, lenAddr());
|
||||||
|
return {v, et, false, owning(et)};
|
||||||
|
}
|
||||||
|
if (n == "insert") {
|
||||||
|
need(2);
|
||||||
|
Value *i = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the position").v;
|
||||||
|
Value *v = elemValue(1);
|
||||||
|
b.CreateCall(rt("finch_arr_insert_gap"), {a, i, elemSize(et), fileName(p), b.getInt64(p.line)});
|
||||||
|
b.CreateStore(v, b.CreateInBoundsGEP(ET, data(), i));
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "remove") {
|
||||||
|
need(1);
|
||||||
|
Value *i = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the position").v;
|
||||||
|
boundsCheck(i, len(), p);
|
||||||
|
Value *v = b.CreateLoad(ET, b.CreateInBoundsGEP(ET, data(), i));
|
||||||
|
b.CreateCall(rt("finch_arr_remove_gap"), {a, i, elemSize(et)});
|
||||||
|
return {v, et, false, owning(et)};
|
||||||
|
}
|
||||||
|
if (n == "clear" || n == "resize") {
|
||||||
|
need(n == "clear" ? 0 : 1);
|
||||||
|
Value *nl = n == "clear" ? (Value *)b.getInt64(0) : coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the new length").v;
|
||||||
|
if (owning(et)) { // drop the elements that go away
|
||||||
|
Value *l = len(), *d = data();
|
||||||
|
Value *from = b.CreateSelect(b.CreateICmpSLT(nl, l), b.CreateSelect(b.CreateICmpSLT(nl, b.getInt64(0)), b.getInt64(0), nl), l);
|
||||||
|
forN(b.CreateSub(l, from), [&](Value *k) { dropAt(b.CreateInBoundsGEP(ET, d, b.CreateAdd(from, k)), et); });
|
||||||
|
}
|
||||||
|
b.CreateCall(rt("finch_arr_resize"), {a, nl, elemSize(et)});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "find" || n == "contains") {
|
||||||
|
need(1);
|
||||||
|
if (!comparable(et)) failAt(p.file, p.line, p.col, n + "() works on arrays of numbers, chars, bools or str");
|
||||||
|
Value_ x = coerce(exprWant(*m.args[0], et), et, m.args[0]->pos, "the value to look for");
|
||||||
|
Value *xv = x.v;
|
||||||
|
Value *res = tmpOf(b.getInt64Ty());
|
||||||
|
b.CreateStore(b.getInt64(-1), res);
|
||||||
|
Value *l = len(), *d = data();
|
||||||
|
Value *i = tmpOf(b.getInt64Ty());
|
||||||
|
b.CreateStore(b.getInt64(0), i);
|
||||||
|
BasicBlock *cond = newBlock("find.cond"), *body = newBlock("find.body"), *hit = newBlock("find.hit"),
|
||||||
|
*next = newBlock("find.next"), *end = newBlock("find.end");
|
||||||
|
b.CreateBr(cond);
|
||||||
|
b.SetInsertPoint(cond);
|
||||||
|
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
||||||
|
b.CreateCondBr(b.CreateICmpSLT(iv, l), body, end);
|
||||||
|
b.SetInsertPoint(body);
|
||||||
|
Value_ ev{b.CreateLoad(ET, b.CreateInBoundsGEP(ET, d, iv)), et};
|
||||||
|
Value *eq = compare(BinOp::Eq, ev, {xv, et}, p).v;
|
||||||
|
b.CreateCondBr(eq, hit, next);
|
||||||
|
b.SetInsertPoint(hit);
|
||||||
|
b.CreateStore(iv, res);
|
||||||
|
b.CreateBr(end);
|
||||||
|
b.SetInsertPoint(next);
|
||||||
|
b.CreateStore(b.CreateAdd(iv, b.getInt64(1)), i);
|
||||||
|
b.CreateBr(cond);
|
||||||
|
b.SetInsertPoint(end);
|
||||||
|
release(x);
|
||||||
|
Value *r = b.CreateLoad(b.getInt64Ty(), res);
|
||||||
|
if (n == "find") return {r, FType::I64};
|
||||||
|
return {b.CreateICmpSGE(r, b.getInt64(0)), FType::Bool};
|
||||||
|
}
|
||||||
|
if (n == "join") {
|
||||||
|
need(1);
|
||||||
|
if (et.kind != FType::Str) failAt(p.file, p.line, p.col, "join() works on an array of str");
|
||||||
|
Value_ sep = coerce(expr(*m.args[0]), FType::Str, m.args[0]->pos, "the separator");
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
b.CreateCall(rt("finch_str_join"), {out, a, tmp(sep.v)});
|
||||||
|
release(sep);
|
||||||
|
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||||
|
}
|
||||||
|
if (n == "slice") {
|
||||||
|
need(2);
|
||||||
|
Value *s = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the start").v;
|
||||||
|
Value *e = coerce(expr(*m.args[1]), FType::I64, m.args[1]->pos, "the end").v;
|
||||||
|
Value *l = len();
|
||||||
|
panicIf(b.CreateOr(b.CreateOr(b.CreateICmpSLT(s, b.getInt64(0)), b.CreateICmpSGT(e, l)), b.CreateICmpSGT(s, e)),
|
||||||
|
"slice(start, end) is out of range", p);
|
||||||
|
Value *cnt = b.CreateSub(e, s);
|
||||||
|
Value *out = tmpOf(AT);
|
||||||
|
b.CreateCall(rt("finch_arr_make"), {out, cnt, elemSize(et)});
|
||||||
|
Value *src = data();
|
||||||
|
Value *dst = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(AT, out, 0));
|
||||||
|
forN(cnt, [&](Value *k) {
|
||||||
|
copyAt(b.CreateInBoundsGEP(ET, dst, k), b.CreateInBoundsGEP(ET, src, b.CreateAdd(s, k)), et);
|
||||||
|
});
|
||||||
|
return {b.CreateLoad(AT, out), at, false, true};
|
||||||
|
}
|
||||||
|
if (n == "reverse") {
|
||||||
|
need(0);
|
||||||
|
Value *l = len(), *d = data();
|
||||||
|
forN(b.CreateSDiv(l, b.getInt64(2)), [&](Value *k) {
|
||||||
|
Value *x = b.CreateInBoundsGEP(ET, d, k);
|
||||||
|
Value *y = b.CreateInBoundsGEP(ET, d, b.CreateSub(b.CreateSub(l, b.getInt64(1)), k));
|
||||||
|
Value *xv = b.CreateLoad(ET, x), *yv = b.CreateLoad(ET, y);
|
||||||
|
b.CreateStore(yv, x);
|
||||||
|
b.CreateStore(xv, y);
|
||||||
|
});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
if (n == "sort") {
|
||||||
|
need(0);
|
||||||
|
if (!comparable(et) || et.kind == FType::Bool) failAt(p.file, p.line, p.col, "sort() works on arrays of numbers, chars or str");
|
||||||
|
std::string key = "cmp." + typeKey(et);
|
||||||
|
Function *cmp = helpers[key];
|
||||||
|
if (!cmp) {
|
||||||
|
cmp = Function::Create(FunctionType::get(b.getInt32Ty(), {b.getPtrTy(), b.getPtrTy()}, false),
|
||||||
|
Function::InternalLinkage, "finch." + key, mod.get());
|
||||||
|
helpers[key] = cmp;
|
||||||
|
IRBuilderBase::InsertPointGuard g(b);
|
||||||
|
DISubprogram *savedFn = diFn;
|
||||||
|
diFn = nullptr;
|
||||||
|
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", cmp));
|
||||||
|
b.SetCurrentDebugLocation(DebugLoc());
|
||||||
|
Value *x = cmp->getArg(0), *y = cmp->getArg(1);
|
||||||
|
Value *r;
|
||||||
|
if (et.kind == FType::Str) {
|
||||||
|
r = b.CreateTrunc(b.CreateCall(rt("finch_str_cmp"), {x, y}), b.getInt32Ty());
|
||||||
|
} else {
|
||||||
|
Value_ xv{b.CreateLoad(ET, x), et}, yv{b.CreateLoad(ET, y), et};
|
||||||
|
Value *lt = compare(BinOp::Less, xv, yv, p).v, *gt = compare(BinOp::Greater, xv, yv, p).v;
|
||||||
|
r = b.CreateSub(b.CreateZExt(gt, b.getInt32Ty()), b.CreateZExt(lt, b.getInt32Ty()));
|
||||||
|
}
|
||||||
|
b.CreateRet(r);
|
||||||
|
diFn = savedFn;
|
||||||
|
}
|
||||||
|
b.CreateCall(libc("qsort", b.getVoidTy(), {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty(), b.getPtrTy()}),
|
||||||
|
{data(), len(), elemSize(et), cmp});
|
||||||
|
return {nullptr, FType::Void};
|
||||||
|
}
|
||||||
|
failAt(p.file, p.line, p.col, "an array has no method '" + n + "' (it has: push, pop, insert, remove, clear, resize, find, contains, join, slice, reverse, sort)");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace finch
|
||||||
265
src/codegen_impl.h
Normal file
265
src/codegen_impl.h
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
#pragma once
|
||||||
|
// Internal to the code generator: the Codegen class, shared by codegen*.cpp and abi.cpp.
|
||||||
|
|
||||||
|
#include "codegen.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
#include <llvm/IR/Constants.h>
|
||||||
|
#include <llvm/IR/DIBuilder.h>
|
||||||
|
#include <llvm/IR/IRBuilder.h>
|
||||||
|
#include <llvm/IR/Verifier.h>
|
||||||
|
#include <llvm/Support/raw_ostream.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using FType = ::Type; // Finch types (llvm::Type is the LLVM one)
|
||||||
|
|
||||||
|
struct StructInfo {
|
||||||
|
std::string name;
|
||||||
|
std::string module; // "" main file, a module name, or "C" for imported C structs
|
||||||
|
bool isC = false;
|
||||||
|
const StructDecl *decl = nullptr; // Finch structs
|
||||||
|
const CStruct *cdecl = nullptr; // C structs
|
||||||
|
struct F {
|
||||||
|
std::string name;
|
||||||
|
FType type;
|
||||||
|
const Expr *init = nullptr; // default value
|
||||||
|
bool hidden = false; // C union/bitfield padding: has no name in Finch
|
||||||
|
unsigned llvmIndex = 0; // C structs carry explicit padding between fields
|
||||||
|
};
|
||||||
|
std::vector<F> fields;
|
||||||
|
llvm::StructType *llvm = nullptr;
|
||||||
|
int state = 0; // 0 = not resolved, 1 = resolving (cycle check), 2 = done
|
||||||
|
bool owning = false;
|
||||||
|
std::string unsupported; // C structs Finch can't lay out
|
||||||
|
Pos pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace finch {
|
||||||
|
|
||||||
|
struct Value_ {
|
||||||
|
llvm::Value *v;
|
||||||
|
FType type;
|
||||||
|
bool literal = false; // a number written in the code (or a C constant): adapts to the type around it
|
||||||
|
bool fresh = false; // an owning value nobody holds yet: it must be stored (moved) or released
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Var {
|
||||||
|
llvm::Value *slot;
|
||||||
|
FType type;
|
||||||
|
bool readonly = false; // loop variables
|
||||||
|
std::string readonlyWhy;
|
||||||
|
bool owned = false; // dropped when its scope ends
|
||||||
|
int order = 0; // declaration order, for defer
|
||||||
|
bool moved = false; // returned out of the function: not dropped on that path
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cleanup {
|
||||||
|
bool isDefer;
|
||||||
|
std::string var; // Drop: the variable
|
||||||
|
const Stmt *body = nullptr; // Defer: the statement
|
||||||
|
int order = 0; // Defer: variables declared after it are not visible
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Scope {
|
||||||
|
std::unordered_map<std::string, Var> vars;
|
||||||
|
std::vector<Cleanup> cleanups;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Fn {
|
||||||
|
llvm::Function *llvm = nullptr;
|
||||||
|
const FnDecl *decl = nullptr;
|
||||||
|
std::string module;
|
||||||
|
std::vector<bool> borrowParam; // owning params the function never changes: no copy
|
||||||
|
llvm::Function *cThunk = nullptr;
|
||||||
|
FType ret; // resolved types
|
||||||
|
std::vector<FType> params;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Loop {
|
||||||
|
llvm::BasicBlock *continueTo, *breakTo;
|
||||||
|
size_t scopeDepth; // scopes opened inside the loop are cleaned up by break/continue
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Place { // something that can be assigned to
|
||||||
|
llvm::Value *addr;
|
||||||
|
FType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LRef {
|
||||||
|
bool isPlace;
|
||||||
|
Place pl;
|
||||||
|
Value_ val;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModuleScope {
|
||||||
|
std::map<std::string, Fn> fns;
|
||||||
|
std::map<std::string, StructInfo *> structs;
|
||||||
|
std::set<std::string> imports; // Finch modules visible here
|
||||||
|
};
|
||||||
|
|
||||||
|
// How a C struct travels through registers (System V x86-64).
|
||||||
|
struct AbiArg {
|
||||||
|
enum Kind { Direct, Expand, Memory } kind = Direct;
|
||||||
|
std::vector<llvm::Type *> parts; // Expand: one value per eightbyte
|
||||||
|
llvm::Type *coerced = nullptr; // Expand: { parts... } for returns
|
||||||
|
};
|
||||||
|
|
||||||
|
class Codegen {
|
||||||
|
public:
|
||||||
|
Codegen(llvm::LLVMContext &c, const std::vector<Program> &progs, const CImports &ci, llvm::TargetMachine &tm,
|
||||||
|
bool debug);
|
||||||
|
std::unique_ptr<llvm::Module> run();
|
||||||
|
|
||||||
|
// ---------- state ----------
|
||||||
|
llvm::LLVMContext &ctx;
|
||||||
|
llvm::IRBuilder<> b;
|
||||||
|
std::unique_ptr<llvm::Module> mod;
|
||||||
|
const std::vector<Program> &progs;
|
||||||
|
const CImports &cimports;
|
||||||
|
const llvm::DataLayout *dl;
|
||||||
|
|
||||||
|
std::map<std::string, ModuleScope> modules;
|
||||||
|
std::map<std::string, StructInfo *> cStructs;
|
||||||
|
std::vector<std::unique_ptr<StructInfo>> structStore;
|
||||||
|
std::vector<Scope> scopes;
|
||||||
|
std::vector<Loop> loops;
|
||||||
|
const Fn *curFn = nullptr;
|
||||||
|
std::string curModule;
|
||||||
|
int varOrder = 0;
|
||||||
|
int deferLimit = -1; // while emitting a defer: hide variables declared after it
|
||||||
|
int inDefer = 0;
|
||||||
|
std::map<std::string, llvm::Function *> helpers; // drop/copy/print per type
|
||||||
|
|
||||||
|
// debug info (-g)
|
||||||
|
bool debug;
|
||||||
|
std::unique_ptr<llvm::DIBuilder> di;
|
||||||
|
std::vector<llvm::DIFile *> diFiles;
|
||||||
|
llvm::DICompileUnit *diUnit = nullptr;
|
||||||
|
llvm::DISubprogram *diFn = nullptr;
|
||||||
|
std::map<std::string, llvm::DIType *> diTypes;
|
||||||
|
|
||||||
|
// ---------- codegen.cpp: program, statements, expressions ----------
|
||||||
|
void declareStructs();
|
||||||
|
void declareFns();
|
||||||
|
void declare(const Program &p, const FnDecl &fn);
|
||||||
|
void define(Fn &fn);
|
||||||
|
void defineMainWrapper();
|
||||||
|
void block(const BlockStmt &blk);
|
||||||
|
void blockBody(const BlockStmt &blk);
|
||||||
|
void stmt(const Stmt &s);
|
||||||
|
void varDecl(const VarDeclStmt &s);
|
||||||
|
void assign(const AssignStmt &s);
|
||||||
|
void ifStmt(const IfStmt &s);
|
||||||
|
void whileStmt(const WhileStmt &s);
|
||||||
|
void forStmt(const ForStmt &s);
|
||||||
|
void forEachStmt(const ForEachStmt &s);
|
||||||
|
void returnStmt(const ReturnStmt &s);
|
||||||
|
void jump(bool isBreak, Pos p);
|
||||||
|
llvm::Value *condition(const Expr &e);
|
||||||
|
void continueAt(llvm::BasicBlock *bb, llvm::BasicBlock *deadEnd);
|
||||||
|
bool terminated();
|
||||||
|
llvm::BasicBlock *newBlock(const char *name);
|
||||||
|
|
||||||
|
void pushScope();
|
||||||
|
void popScope(); // emits the scope's cleanups if reachable
|
||||||
|
void emitCleanups(size_t downTo); // run cleanups of scopes [downTo, end) without popping
|
||||||
|
void emitScopeCleanups(size_t idx);
|
||||||
|
llvm::AllocaInst *slot(const FType &t, const std::string &name);
|
||||||
|
Var *lookup(const std::string &name);
|
||||||
|
Var &addVar(Pos p, const std::string &name, const FType &t, llvm::Value *init, bool owned, bool readonly = false,
|
||||||
|
unsigned argNo = 0);
|
||||||
|
|
||||||
|
Value_ expr(const Expr &e);
|
||||||
|
Value_ exprWant(const Expr &e, const FType &want);
|
||||||
|
Value_ binary(const BinaryExpr &e);
|
||||||
|
Value_ unary(const UnaryExpr &e);
|
||||||
|
Value_ logic(const BinaryExpr &e);
|
||||||
|
Value_ arith(BinOp op, Value_ l, Value_ r, Pos p);
|
||||||
|
Value_ arithOp(BinOp op, Value_ l, Value_ r, Pos p);
|
||||||
|
Value_ compare(BinOp op, Value_ l, Value_ r, Pos p);
|
||||||
|
bool unify(Value_ &l, Value_ &r);
|
||||||
|
[[noreturn]] void badOperands(BinOp op, const Value_ &l, const Value_ &r, Pos p);
|
||||||
|
LRef ref(const Expr &e, bool forWrite = false); // an address if `e` has one, else a value
|
||||||
|
Place place(const Expr &e, bool forWrite = false);
|
||||||
|
LRef member(const MemberExpr &m, bool forWrite);
|
||||||
|
LRef index(const IndexExpr &e, bool forWrite);
|
||||||
|
Value_ arrayLit(const ArrayLitExpr &a, const FType *want);
|
||||||
|
std::string rootVar(const Expr &e);
|
||||||
|
bool mutates(const std::string &name, const Stmt &s);
|
||||||
|
bool mutatesExpr(const std::string &name, const Expr &e);
|
||||||
|
|
||||||
|
// ---------- codegen_types.cpp: types, conversions, ownership, printing ----------
|
||||||
|
llvm::Type *ty(const FType &t);
|
||||||
|
FType resolve(const FType &t, Pos p);
|
||||||
|
FType resolveT(const FType &t, Pos p, bool deep); // deep: structs by value must be complete
|
||||||
|
void forN(llvm::Value *n, const std::function<void(llvm::Value *)> &body); // for i in 0..n
|
||||||
|
llvm::Value *tmpOf(llvm::Type *t); // an uninitialised stack slot
|
||||||
|
void resolveStruct(StructInfo *s);
|
||||||
|
StructInfo *findStruct(const std::string &module, const std::string &name, bool qualified);
|
||||||
|
bool owning(const FType &t);
|
||||||
|
llvm::Constant *zero(const FType &t);
|
||||||
|
Value_ defaultValue(const FType &t, Pos p);
|
||||||
|
static bool widens(const FType &from, const FType &to);
|
||||||
|
static bool fits(llvm::ConstantInt *c, const FType &from, const FType &to);
|
||||||
|
llvm::Value *intCast(llvm::Value *v, const FType &from, const FType &to);
|
||||||
|
llvm::Value *intToFloat(llvm::Value *v, const FType &from, const FType &to);
|
||||||
|
Value_ coerce(Value_ v, const FType &want, Pos p, const std::string &what);
|
||||||
|
llvm::Value *own(const Value_ &v); // something that can be stored: moved if fresh, else copied
|
||||||
|
void release(const Value_ &v); // drop a fresh value nobody kept
|
||||||
|
llvm::Value *copyValue(llvm::Value *v, const FType &t);
|
||||||
|
void dropValue(llvm::Value *v, const FType &t);
|
||||||
|
void dropAt(llvm::Value *addr, const FType &t);
|
||||||
|
void copyAt(llvm::Value *dst, llvm::Value *src, const FType &t);
|
||||||
|
llvm::Function *dropFn(const FType &t);
|
||||||
|
llvm::Function *copyFn(const FType &t);
|
||||||
|
std::string typeKey(const FType &t);
|
||||||
|
llvm::Value *tmp(llvm::Value *v); // a stack copy of v, to pass by address
|
||||||
|
void emitPrint(llvm::Value *v, const FType &t, bool quoted);
|
||||||
|
llvm::Function *printFn(const FType &t);
|
||||||
|
void printf_(const std::string &fmt, std::vector<llvm::Value *> args);
|
||||||
|
llvm::Value *strConst(const std::string &s);
|
||||||
|
llvm::Value *cstr(llvm::Value *strValue); // str -> char* for C
|
||||||
|
Value_ strFromC(llvm::Value *p); // char* -> borrowed str
|
||||||
|
llvm::Value *elemSize(const FType &t);
|
||||||
|
|
||||||
|
// ---------- codegen_builtins.cpp: calls, built-ins, methods ----------
|
||||||
|
Value_ call(const CallExpr &c);
|
||||||
|
Value_ method(const MethodExpr &m);
|
||||||
|
Value_ callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
|
||||||
|
const std::string &shownName);
|
||||||
|
Value_ construct(StructInfo *s, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p);
|
||||||
|
Value_ callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p);
|
||||||
|
Value_ convert(const FType &to, const std::vector<ExprPtr> &args, Pos p, const std::string &name);
|
||||||
|
Value_ print(const std::vector<ExprPtr> &args, Pos p);
|
||||||
|
Value_ addrOf(const std::vector<ExprPtr> &args, Pos p);
|
||||||
|
Value_ arrayMethod(const MethodExpr &m, Value_ *recv);
|
||||||
|
bool isBuiltin(const std::string &n);
|
||||||
|
std::string headerHint(const std::string &name);
|
||||||
|
void checkArgs(const std::vector<ExprPtr> &args, const std::vector<std::string> &names, size_t want, Pos p,
|
||||||
|
const std::string &name, bool variadic = false);
|
||||||
|
llvm::Value *cGlobal(const std::string &name);
|
||||||
|
llvm::Function *rt(const std::string &name); // a runtime function by name
|
||||||
|
|
||||||
|
// runtime errors
|
||||||
|
llvm::Value *fileName(Pos p);
|
||||||
|
void panicIf(llvm::Value *cond, const std::string &msg, Pos p);
|
||||||
|
void boundsCheck(llvm::Value *i, llvm::Value *len, Pos p);
|
||||||
|
void checkDivisor(const Value_ &l, const Value_ &r, Pos p);
|
||||||
|
llvm::Function *libc(const char *name, llvm::Type *ret, std::vector<llvm::Type *> params, bool vararg = false);
|
||||||
|
|
||||||
|
// ---------- abi.cpp: C calling convention for structs by value ----------
|
||||||
|
AbiArg classify(const FType &t);
|
||||||
|
llvm::Function *declareC(const CFunc &f);
|
||||||
|
Value_ emitCCall(const CFunc &f, llvm::Function *fn, std::vector<Value_> args, Pos p);
|
||||||
|
llvm::Function *cThunk(Fn &fn, Pos p);
|
||||||
|
|
||||||
|
// ---------- debug info ----------
|
||||||
|
void setLoc(Pos p);
|
||||||
|
llvm::DIType *diType(const FType &t);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace finch
|
||||||
500
src/codegen_types.cpp
Normal file
500
src/codegen_types.cpp
Normal file
|
|
@ -0,0 +1,500 @@
|
||||||
|
// Code generation, part 2: types, conversions, ownership (copy/drop) and printing.
|
||||||
|
|
||||||
|
#include "codegen_impl.h"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
|
||||||
|
namespace finch {
|
||||||
|
|
||||||
|
// ---------- types ----------
|
||||||
|
|
||||||
|
llvm::Type *Codegen::ty(const FType &t) {
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Void: return b.getVoidTy();
|
||||||
|
case FType::Bool: return b.getInt1Ty();
|
||||||
|
case FType::Char:
|
||||||
|
case FType::I8:
|
||||||
|
case FType::U8: return b.getInt8Ty();
|
||||||
|
case FType::I16:
|
||||||
|
case FType::U16: return b.getInt16Ty();
|
||||||
|
case FType::I32:
|
||||||
|
case FType::U32: return b.getInt32Ty();
|
||||||
|
case FType::I64:
|
||||||
|
case FType::U64: return b.getInt64Ty();
|
||||||
|
case FType::F32: return b.getFloatTy();
|
||||||
|
case FType::F64: return b.getDoubleTy();
|
||||||
|
case FType::Str:
|
||||||
|
case FType::Array: return StructType::get(ctx, {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty()});
|
||||||
|
case FType::Ptr:
|
||||||
|
case FType::Null: return b.getPtrTy();
|
||||||
|
case FType::Fixed: return ArrayType::get(ty(*t.elem), t.count);
|
||||||
|
case FType::Struct: return t.info->llvm;
|
||||||
|
case FType::Named: break;
|
||||||
|
}
|
||||||
|
std::fprintf(stderr, "internal compiler error: unresolved type %s\n", t.show().c_str());
|
||||||
|
std::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
FType Codegen::resolve(const FType &t, Pos p) { return resolveT(t, p, true); }
|
||||||
|
|
||||||
|
StructInfo *Codegen::findStruct(const std::string &module, const std::string &name, bool qualified) {
|
||||||
|
if (qualified) {
|
||||||
|
if (module == "C") {
|
||||||
|
auto it = cStructs.find(name);
|
||||||
|
return it == cStructs.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
auto m = modules.find(module);
|
||||||
|
if (m == modules.end()) return nullptr;
|
||||||
|
auto it = m->second.structs.find(name);
|
||||||
|
return it == m->second.structs.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
auto &here = modules[curModule].structs;
|
||||||
|
if (auto it = here.find(name); it != here.end()) return it->second;
|
||||||
|
if (auto it = cStructs.find(name); it != cStructs.end()) return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FType Codegen::resolveT(const FType &t, Pos p, bool deep) {
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Ptr:
|
||||||
|
if (!t.elem) return t;
|
||||||
|
return FType::ptrTo(resolveT(*t.elem, p, false));
|
||||||
|
case FType::Array: return FType::arrayOf(resolveT(*t.elem, p, false));
|
||||||
|
case FType::Fixed: {
|
||||||
|
FType f = t;
|
||||||
|
f.elem = std::make_shared<FType>(resolveT(*t.elem, p, deep));
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
case FType::Named: {
|
||||||
|
bool qualified = !t.module.empty();
|
||||||
|
if (qualified && t.module != "C" && !modules[curModule].imports.count(t.module))
|
||||||
|
failAt(p.file, p.line, p.col, "'" + t.module + "' is not imported here (add: import " + t.module + ")");
|
||||||
|
StructInfo *s = findStruct(t.module, t.name, qualified);
|
||||||
|
if (!s) failAt(p.file, p.line, p.col, "there is no type named '" + t.show() + "'");
|
||||||
|
FType r(FType::Struct);
|
||||||
|
r.info = s;
|
||||||
|
r.name = s->name;
|
||||||
|
r.module = s->module == "C" ? "" : s->module; // shown as math.Vec; C and main-file structs by name
|
||||||
|
if (deep) resolveStruct(s);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
default: return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::resolveStruct(StructInfo *s) {
|
||||||
|
if (s->state == 2) return;
|
||||||
|
if (s->state == 1)
|
||||||
|
failAt(s->pos.file, s->pos.line, s->pos.col,
|
||||||
|
"the struct " + s->name + " contains itself; use ptr[" + s->name + "] or []" + s->name + " for that field");
|
||||||
|
s->state = 1;
|
||||||
|
std::string savedModule = curModule;
|
||||||
|
curModule = s->isC ? "" : s->module;
|
||||||
|
|
||||||
|
std::vector<llvm::Type *> elems;
|
||||||
|
if (!s->isC) {
|
||||||
|
for (const Field &f : s->decl->fields) {
|
||||||
|
StructInfo::F nf;
|
||||||
|
nf.name = f.name;
|
||||||
|
nf.type = resolveT(f.type, f.pos, true);
|
||||||
|
if (nf.type.kind == FType::Void) failAt(f.pos.file, f.pos.line, f.pos.col, "a field can't have the type 'nothing'");
|
||||||
|
nf.init = f.init.get();
|
||||||
|
nf.llvmIndex = elems.size();
|
||||||
|
elems.push_back(ty(nf.type));
|
||||||
|
s->owning |= owning(nf.type);
|
||||||
|
s->fields.push_back(nf);
|
||||||
|
}
|
||||||
|
s->llvm->setBody(elems);
|
||||||
|
} else {
|
||||||
|
// Lay the C struct out byte for byte: explicit padding, packed, exactly C's offsets.
|
||||||
|
long long off = 0;
|
||||||
|
Pos none;
|
||||||
|
for (const CField &cf : s->cdecl->fields) {
|
||||||
|
if (cf.hidden || cf.offset < off) continue;
|
||||||
|
FType ft = resolveT(cf.type, none, true);
|
||||||
|
llvm::Type *lt = ty(ft);
|
||||||
|
long long size = dl->getTypeAllocSize(lt);
|
||||||
|
if (size != cf.size) continue; // something Finch can't mirror; the padding covers it
|
||||||
|
if (cf.offset > off) elems.push_back(ArrayType::get(b.getInt8Ty(), cf.offset - off));
|
||||||
|
StructInfo::F nf;
|
||||||
|
nf.name = cf.name;
|
||||||
|
nf.type = ft;
|
||||||
|
nf.llvmIndex = elems.size();
|
||||||
|
elems.push_back(lt);
|
||||||
|
s->fields.push_back(nf);
|
||||||
|
off = cf.offset + size;
|
||||||
|
}
|
||||||
|
if (s->cdecl->size > off) elems.push_back(ArrayType::get(b.getInt8Ty(), s->cdecl->size - off));
|
||||||
|
s->llvm->setBody(elems, true);
|
||||||
|
if (s->cdecl->hasHidden && s->unsupported.empty()) s->unsupported = "has fields Finch can't see (unions or bit fields)";
|
||||||
|
}
|
||||||
|
curModule = savedModule;
|
||||||
|
s->state = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Codegen::owning(const FType &t) {
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Str:
|
||||||
|
case FType::Array: return true;
|
||||||
|
case FType::Struct:
|
||||||
|
resolveStruct(t.info);
|
||||||
|
return t.info->owning;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Constant *Codegen::zero(const FType &t) { return Constant::getNullValue(ty(t)); }
|
||||||
|
|
||||||
|
Value_ Codegen::defaultValue(const FType &t, Pos p) {
|
||||||
|
if (t.kind == FType::Struct && !t.info->isC) {
|
||||||
|
for (auto &f : t.info->fields)
|
||||||
|
if (f.init) return construct(t.info, {}, {}, p);
|
||||||
|
}
|
||||||
|
return {zero(t), t, false, owning(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can every value of `from` be stored in `to`? (i8 -> i32 yes, i32 -> u32 no)
|
||||||
|
bool Codegen::widens(const FType &from, const FType &to) {
|
||||||
|
if (!from.isInt() || !to.isInt()) return false;
|
||||||
|
if (from.isSigned() && to.isUnsigned()) return false;
|
||||||
|
return to.bits() > from.bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the constant number `c` (of type `from`) fit into `to`?
|
||||||
|
bool Codegen::fits(ConstantInt *c, const FType &from, const FType &to) {
|
||||||
|
if (!to.isInt()) return false;
|
||||||
|
bool negative = from.isSigned() && c->isNegative();
|
||||||
|
if (negative && to.isUnsigned()) return false;
|
||||||
|
if (negative) return c->getSExtValue() >= -(1LL << (to.bits() - 1));
|
||||||
|
unsigned long long v = c->getZExtValue();
|
||||||
|
if (to.bits() == 64) return to.isUnsigned() || v <= (unsigned long long)INT64_MAX;
|
||||||
|
unsigned long long max = to.isUnsigned() ? (1ULL << to.bits()) - 1 : (1ULL << (to.bits() - 1)) - 1;
|
||||||
|
return v <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::intCast(Value *v, const FType &from, const FType &to) { return b.CreateIntCast(v, ty(to), from.isSigned()); }
|
||||||
|
|
||||||
|
Value *Codegen::intToFloat(Value *v, const FType &from, const FType &to) {
|
||||||
|
return from.isSigned() ? b.CreateSIToFP(v, ty(to)) : b.CreateUIToFP(v, ty(to));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit conversions: only the ones that can't surprise you.
|
||||||
|
Value_ Codegen::coerce(Value_ v, const FType &want, Pos p, const std::string &what) {
|
||||||
|
const FType &from = v.type;
|
||||||
|
if (from == want) return v;
|
||||||
|
if (from.kind == FType::Null && want.isPtr()) return {ConstantPointerNull::get(b.getPtrTy()), want};
|
||||||
|
// any pointer fits an untyped `ptr`; an untyped ptr fits any pointer
|
||||||
|
if (want.isPtr() && from.isPtr() && (!want.elem || !from.elem)) return {v.v, want};
|
||||||
|
|
||||||
|
if (from.isInt() && want.isInt()) {
|
||||||
|
auto *c = v.literal ? dyn_cast<ConstantInt>(v.v) : nullptr;
|
||||||
|
if ((c && fits(c, from, want)) || widens(from, want)) return {intCast(v.v, from, want), want, (bool)c};
|
||||||
|
if (c)
|
||||||
|
failAt(p.file, p.line, p.col, "the number " + std::to_string(from.isSigned() ? c->getSExtValue() : (long long)c->getZExtValue()) +
|
||||||
|
" doesn't fit in " + want.show());
|
||||||
|
}
|
||||||
|
if (from.isInt() && want.isFloat()) return {intToFloat(v.v, from, want), want, v.literal};
|
||||||
|
if (from.kind == FType::F32 && want.kind == FType::F64) return {b.CreateFPExt(v.v, ty(want)), want};
|
||||||
|
if (from.kind == FType::F64 && want.kind == FType::F32 && v.literal) return {b.CreateFPTrunc(v.v, ty(want)), want, true};
|
||||||
|
|
||||||
|
std::string msg = what + " must be " + want.show() + ", but this is " + from.show();
|
||||||
|
if (from.isNumber() && want.isNumber())
|
||||||
|
msg += " (use " + want.show() + "(...) to convert" + (from.isFloat() && want.isInt() ? ", it cuts off the fraction)" : ")");
|
||||||
|
else if (want.kind == FType::Str && (from.isNumber() || from.kind == FType::Bool || from.kind == FType::Char))
|
||||||
|
msg += " (turn it into text with str(...))";
|
||||||
|
else if (want.isPtr() && from.kind == FType::Str)
|
||||||
|
msg += " (use .ptr to get the text's address)";
|
||||||
|
if (from.kind == FType::Null) msg = what + " can't be null (only pointers can)";
|
||||||
|
failAt(p.file, p.line, p.col, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- ownership ----------
|
||||||
|
|
||||||
|
Value *Codegen::tmpOf(llvm::Type *t) {
|
||||||
|
Function *f = b.GetInsertBlock()->getParent();
|
||||||
|
IRBuilder<> entry(&f->getEntryBlock(), f->getEntryBlock().begin());
|
||||||
|
AllocaInst *a = entry.CreateAlloca(t);
|
||||||
|
if (t->isStructTy()) a->setAlignment(Align(16));
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::tmp(Value *v) {
|
||||||
|
Value *a = tmpOf(v->getType());
|
||||||
|
b.CreateStore(v, a);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::own(const Value_ &v) {
|
||||||
|
if (!owning(v.type) || v.fresh) return v.v;
|
||||||
|
return copyValue(v.v, v.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::release(const Value_ &v) {
|
||||||
|
if (v.fresh && v.v && owning(v.type)) dropValue(v.v, v.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::copyValue(Value *v, const FType &t) {
|
||||||
|
if (!owning(t)) return v;
|
||||||
|
Value *out = tmpOf(ty(t));
|
||||||
|
copyAt(out, tmp(v), t);
|
||||||
|
return b.CreateLoad(ty(t), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::dropValue(Value *v, const FType &t) {
|
||||||
|
if (owning(t)) dropAt(tmp(v), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::dropAt(Value *addr, const FType &t) {
|
||||||
|
if (!owning(t)) return;
|
||||||
|
if (t.kind == FType::Str) b.CreateCall(rt("finch_str_drop"), {addr});
|
||||||
|
else b.CreateCall(dropFn(t), {addr});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::copyAt(Value *dst, Value *src, const FType &t) {
|
||||||
|
if (!owning(t)) {
|
||||||
|
b.CreateStore(b.CreateLoad(ty(t), src), dst);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (t.kind == FType::Str) b.CreateCall(rt("finch_str_copy"), {dst, src});
|
||||||
|
else b.CreateCall(copyFn(t), {dst, src});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::forN(Value *n, const std::function<void(Value *)> &body) {
|
||||||
|
Value *i = tmpOf(b.getInt64Ty());
|
||||||
|
b.CreateStore(b.getInt64(0), i);
|
||||||
|
BasicBlock *cond = newBlock("n.cond"), *loop = newBlock("n.body"), *end = newBlock("n.end");
|
||||||
|
b.CreateBr(cond);
|
||||||
|
b.SetInsertPoint(cond);
|
||||||
|
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
||||||
|
b.CreateCondBr(b.CreateICmpSLT(iv, n), loop, end);
|
||||||
|
b.SetInsertPoint(loop);
|
||||||
|
body(iv);
|
||||||
|
b.CreateStore(b.CreateAdd(iv, b.getInt64(1)), i);
|
||||||
|
b.CreateBr(cond);
|
||||||
|
b.SetInsertPoint(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Codegen::typeKey(const FType &t) {
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Array: return "arr_" + typeKey(*t.elem);
|
||||||
|
case FType::Fixed: return "fix" + std::to_string(t.count) + "_" + typeKey(*t.elem);
|
||||||
|
case FType::Ptr: return "ptr";
|
||||||
|
case FType::Struct: return "S_" + (t.info->module.empty() ? std::string("main") : t.info->module) + "_" + t.info->name;
|
||||||
|
default: return t.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions are generated once per type and shared; they have no source position.
|
||||||
|
struct HelperScope {
|
||||||
|
Codegen &cg;
|
||||||
|
IRBuilderBase::InsertPointGuard guard;
|
||||||
|
DISubprogram *savedFn;
|
||||||
|
HelperScope(Codegen &c, Function *f) : cg(c), guard(c.b), savedFn(c.diFn) {
|
||||||
|
c.diFn = nullptr;
|
||||||
|
c.b.SetInsertPoint(BasicBlock::Create(c.ctx, "entry", f));
|
||||||
|
c.b.SetCurrentDebugLocation(DebugLoc());
|
||||||
|
}
|
||||||
|
~HelperScope() { cg.diFn = savedFn; }
|
||||||
|
};
|
||||||
|
|
||||||
|
Function *Codegen::dropFn(const FType &t) {
|
||||||
|
std::string key = "drop." + typeKey(t);
|
||||||
|
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
|
||||||
|
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::InternalLinkage,
|
||||||
|
"finch." + key, mod.get());
|
||||||
|
helpers[key] = f;
|
||||||
|
HelperScope hs(*this, f);
|
||||||
|
Value *a = f->getArg(0);
|
||||||
|
if (t.kind == FType::Array) {
|
||||||
|
FType et = *t.elem;
|
||||||
|
if (owning(et)) {
|
||||||
|
Value *data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), a, 0));
|
||||||
|
Value *len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), a, 1));
|
||||||
|
forN(len, [&](Value *i) { dropAt(b.CreateInBoundsGEP(ty(et), data, i), et); });
|
||||||
|
}
|
||||||
|
b.CreateCall(rt("finch_arr_free"), {a});
|
||||||
|
} else if (t.kind == FType::Struct) {
|
||||||
|
for (auto &fl : t.info->fields)
|
||||||
|
if (owning(fl.type)) dropAt(b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex), fl.type);
|
||||||
|
}
|
||||||
|
b.CreateRetVoid();
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function *Codegen::copyFn(const FType &t) {
|
||||||
|
std::string key = "copy." + typeKey(t);
|
||||||
|
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
|
||||||
|
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy(), b.getPtrTy()}, false),
|
||||||
|
Function::InternalLinkage, "finch." + key, mod.get());
|
||||||
|
helpers[key] = f;
|
||||||
|
HelperScope hs(*this, f);
|
||||||
|
Value *dst = f->getArg(0), *src = f->getArg(1);
|
||||||
|
if (t.kind == FType::Array) {
|
||||||
|
FType et = *t.elem;
|
||||||
|
b.CreateCall(rt("finch_arr_clone_raw"), {dst, src, elemSize(et)});
|
||||||
|
if (owning(et)) {
|
||||||
|
Value *d = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), dst, 0));
|
||||||
|
Value *s = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), src, 0));
|
||||||
|
Value *len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), src, 1));
|
||||||
|
forN(len, [&](Value *i) {
|
||||||
|
copyAt(b.CreateInBoundsGEP(ty(et), d, i), b.CreateInBoundsGEP(ty(et), s, i), et);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (t.kind == FType::Struct) {
|
||||||
|
b.CreateStore(b.CreateLoad(ty(t), src), dst);
|
||||||
|
for (auto &fl : t.info->fields)
|
||||||
|
if (owning(fl.type))
|
||||||
|
copyAt(b.CreateStructGEP(t.info->llvm, dst, fl.llvmIndex), b.CreateStructGEP(t.info->llvm, src, fl.llvmIndex), fl.type);
|
||||||
|
}
|
||||||
|
b.CreateRetVoid();
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- strings ----------
|
||||||
|
|
||||||
|
Value *Codegen::strConst(const std::string &s) {
|
||||||
|
Constant *g = b.CreateGlobalString(s, "str");
|
||||||
|
return ConstantStruct::get(cast<StructType>(ty(FType::Str)), {g, b.getInt64(s.size()), b.getInt64(0)});
|
||||||
|
}
|
||||||
|
|
||||||
|
// str -> char* for C (an empty str may have no memory at all; C gets "" then)
|
||||||
|
Value *Codegen::cstr(Value *s) {
|
||||||
|
Value *p = b.CreateExtractValue(s, 0);
|
||||||
|
return b.CreateSelect(b.CreateIsNull(p), b.CreateGlobalString("", "empty"), p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value_ Codegen::strFromC(Value *p) {
|
||||||
|
Value *out = tmpOf(ty(FType::Str));
|
||||||
|
b.CreateCall(rt("finch_str_from_c"), {out, p});
|
||||||
|
return {b.CreateLoad(ty(FType::Str), out), FType::Str};
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *Codegen::elemSize(const FType &t) { return b.getInt64(dl->getTypeAllocSize(ty(t))); }
|
||||||
|
|
||||||
|
// ---------- printing ----------
|
||||||
|
|
||||||
|
void Codegen::printf_(const std::string &fmt, std::vector<Value *> args) {
|
||||||
|
args.insert(args.begin(), b.CreateGlobalString(fmt, "fmt"));
|
||||||
|
b.CreateCall(libc("printf", b.getInt32Ty(), {b.getPtrTy()}, true), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Codegen::emitPrint(Value *v, const FType &t, bool quoted) {
|
||||||
|
if (t.isSigned()) printf_("%lld", {b.CreateSExt(v, b.getInt64Ty())});
|
||||||
|
else if (t.isUnsigned()) printf_("%llu", {b.CreateZExt(v, b.getInt64Ty())});
|
||||||
|
else if (t.isFloat()) printf_("%g", {b.CreateFPExt(v, b.getDoubleTy())});
|
||||||
|
else if (t.kind == FType::Char) printf_(quoted ? "'%c'" : "%c", {b.CreateZExt(v, b.getInt32Ty())});
|
||||||
|
else if (t.kind == FType::Bool) printf_("%s", {b.CreateSelect(v, b.CreateGlobalString("true", "true"), b.CreateGlobalString("false", "false"))});
|
||||||
|
else if (t.kind == FType::Str)
|
||||||
|
printf_(quoted ? "\"%.*s\"" : "%.*s", {b.CreateTrunc(b.CreateExtractValue(v, 1), b.getInt32Ty()), cstr(v)});
|
||||||
|
else if (t.isPtr()) printf_("%p", {v});
|
||||||
|
else if (t.kind == FType::Null) printf_("null", {});
|
||||||
|
else b.CreateCall(printFn(t), {tmp(v)});
|
||||||
|
}
|
||||||
|
|
||||||
|
Function *Codegen::printFn(const FType &t) {
|
||||||
|
std::string key = "print." + typeKey(t);
|
||||||
|
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
|
||||||
|
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::InternalLinkage,
|
||||||
|
"finch." + key, mod.get());
|
||||||
|
helpers[key] = f;
|
||||||
|
HelperScope hs(*this, f);
|
||||||
|
Value *a = f->getArg(0);
|
||||||
|
if (t.kind == FType::Array || t.kind == FType::Fixed) {
|
||||||
|
FType et = *t.elem;
|
||||||
|
Value *data, *len;
|
||||||
|
if (t.kind == FType::Fixed) {
|
||||||
|
data = a;
|
||||||
|
len = b.getInt64(t.count);
|
||||||
|
} else {
|
||||||
|
data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), a, 0));
|
||||||
|
len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), a, 1));
|
||||||
|
}
|
||||||
|
printf_("[", {});
|
||||||
|
forN(len, [&](Value *i) {
|
||||||
|
BasicBlock *sep = newBlock("sep"), *item = newBlock("item");
|
||||||
|
b.CreateCondBr(b.CreateICmpSGT(i, b.getInt64(0)), sep, item);
|
||||||
|
b.SetInsertPoint(sep);
|
||||||
|
printf_(", ", {});
|
||||||
|
b.CreateBr(item);
|
||||||
|
b.SetInsertPoint(item);
|
||||||
|
emitPrint(b.CreateLoad(ty(et), b.CreateInBoundsGEP(ty(et), data, i)), et, true);
|
||||||
|
});
|
||||||
|
printf_("]", {});
|
||||||
|
} else if (t.kind == FType::Struct) {
|
||||||
|
printf_(t.info->name + "(", {});
|
||||||
|
bool first = true;
|
||||||
|
for (auto &fl : t.info->fields) {
|
||||||
|
printf_((first ? "" : ", ") + fl.name + ": ", {});
|
||||||
|
first = false;
|
||||||
|
emitPrint(b.CreateLoad(ty(fl.type), b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex)), fl.type, true);
|
||||||
|
}
|
||||||
|
printf_(")", {});
|
||||||
|
}
|
||||||
|
b.CreateRetVoid();
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- debug info ----------
|
||||||
|
|
||||||
|
DIType *Codegen::diType(const FType &t) {
|
||||||
|
std::string key = typeKey(t);
|
||||||
|
if (auto it = diTypes.find(key); it != diTypes.end()) return it->second;
|
||||||
|
DIType *d = nullptr;
|
||||||
|
auto basic = [&](const char *n, unsigned bits, unsigned enc) { return di->createBasicType(n, bits, enc); };
|
||||||
|
switch (t.kind) {
|
||||||
|
case FType::Void: return nullptr;
|
||||||
|
case FType::Bool: d = basic("bool", 8, dwarf::DW_ATE_boolean); break;
|
||||||
|
case FType::Char: d = basic("char", 8, dwarf::DW_ATE_signed_char); break;
|
||||||
|
case FType::I8: d = basic("i8", 8, dwarf::DW_ATE_signed); break;
|
||||||
|
case FType::I16: d = basic("i16", 16, dwarf::DW_ATE_signed); break;
|
||||||
|
case FType::I32: d = basic("i32", 32, dwarf::DW_ATE_signed); break;
|
||||||
|
case FType::I64: d = basic("int", 64, dwarf::DW_ATE_signed); break;
|
||||||
|
case FType::U8: d = basic("u8", 8, dwarf::DW_ATE_unsigned); break;
|
||||||
|
case FType::U16: d = basic("u16", 16, dwarf::DW_ATE_unsigned); break;
|
||||||
|
case FType::U32: d = basic("u32", 32, dwarf::DW_ATE_unsigned); break;
|
||||||
|
case FType::U64: d = basic("u64", 64, dwarf::DW_ATE_unsigned); break;
|
||||||
|
case FType::F32: d = basic("f32", 32, dwarf::DW_ATE_float); break;
|
||||||
|
case FType::F64: d = basic("float", 64, dwarf::DW_ATE_float); break;
|
||||||
|
case FType::Ptr:
|
||||||
|
case FType::Null: d = di->createPointerType(t.elem ? diType(*t.elem) : nullptr, 64, 0, std::nullopt, t.show()); break;
|
||||||
|
case FType::Fixed:
|
||||||
|
d = di->createArrayType(t.count * (uint64_t)dl->getTypeAllocSizeInBits(ty(*t.elem)), 0, diType(*t.elem),
|
||||||
|
di->getOrCreateArray({di->getOrCreateSubrange(0, t.count)}));
|
||||||
|
break;
|
||||||
|
case FType::Str:
|
||||||
|
case FType::Array: {
|
||||||
|
DIType *et = t.kind == FType::Str ? diType(FType::Char) : diType(*t.elem);
|
||||||
|
DIType *pt = di->createPointerType(et, 64);
|
||||||
|
DIType *i64 = diType(FType::I64);
|
||||||
|
DIFile *f = diFiles[0];
|
||||||
|
Metadata *els[] = {di->createMemberType(diUnit, "ptr", f, 0, 64, 64, 0, DINode::FlagZero, pt),
|
||||||
|
di->createMemberType(diUnit, "len", f, 0, 64, 64, 64, DINode::FlagZero, i64),
|
||||||
|
di->createMemberType(diUnit, "cap", f, 0, 64, 64, 128, DINode::FlagZero, i64)};
|
||||||
|
d = di->createStructType(diUnit, t.show(), f, 0, 192, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FType::Struct: {
|
||||||
|
StructInfo *s = t.info;
|
||||||
|
DIFile *f = s->isC ? diFiles[0] : diFiles[s->pos.file];
|
||||||
|
auto *fwd = di->createReplaceableCompositeType(dwarf::DW_TAG_structure_type, s->name, diUnit, f, s->pos.line);
|
||||||
|
diTypes[key] = fwd;
|
||||||
|
const StructLayout *lay = dl->getStructLayout(s->llvm);
|
||||||
|
std::vector<Metadata *> els;
|
||||||
|
for (auto &fl : s->fields) {
|
||||||
|
uint64_t bits = dl->getTypeAllocSizeInBits(ty(fl.type));
|
||||||
|
els.push_back(di->createMemberType(diUnit, fl.name, f, s->pos.line, bits, 8, lay->getElementOffsetInBits(fl.llvmIndex),
|
||||||
|
DINode::FlagZero, diType(fl.type)));
|
||||||
|
}
|
||||||
|
DICompositeType *real = di->createStructType(diUnit, s->name, f, s->pos.line, lay->getSizeInBits(), 8,
|
||||||
|
DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||||
|
di->replaceTemporary(TempDIType(fwd), real);
|
||||||
|
d = real;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FType::Named: return nullptr;
|
||||||
|
}
|
||||||
|
diTypes[key] = d;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace finch
|
||||||
25
src/error.h
25
src/error.h
|
|
@ -3,21 +3,28 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Every source file the compiler has read; positions point into this list.
|
||||||
|
struct SourceFile {
|
||||||
|
std::string path;
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
inline std::vector<SourceFile> g_files;
|
||||||
|
inline int g_curFile = 0; // the file the lexer/parser is working on
|
||||||
|
|
||||||
// Finch stops at the first error and shows exactly where it is.
|
// Finch stops at the first error and shows exactly where it is.
|
||||||
inline std::string g_file;
|
[[noreturn]] inline void failAt(int file, int line, int col, const std::string &msg) {
|
||||||
inline std::string g_source;
|
const SourceFile &f = g_files.at(file);
|
||||||
|
std::fprintf(stderr, "%s:%d:%d: error: %s\n", f.path.c_str(), line, col, msg.c_str());
|
||||||
[[noreturn]] inline void fail(int line, int col, const std::string &msg) {
|
|
||||||
std::fprintf(stderr, "%s:%d:%d: error: %s\n", g_file.c_str(), line, col, msg.c_str());
|
|
||||||
|
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
for (int l = 1; l < line && start != std::string::npos; l++) {
|
for (int l = 1; l < line && start != std::string::npos; l++) {
|
||||||
start = g_source.find('\n', start);
|
start = f.text.find('\n', start);
|
||||||
if (start != std::string::npos) start++;
|
if (start != std::string::npos) start++;
|
||||||
}
|
}
|
||||||
if (start != std::string::npos && start < g_source.size()) {
|
if (line > 0 && start != std::string::npos && start < f.text.size()) {
|
||||||
std::string text = g_source.substr(start, g_source.find('\n', start) - start);
|
std::string text = f.text.substr(start, f.text.find('\n', start) - start);
|
||||||
std::string pad;
|
std::string pad;
|
||||||
int c = 1; // columns count characters; skip UTF-8 continuation bytes
|
int c = 1; // columns count characters; skip UTF-8 continuation bytes
|
||||||
for (size_t k = 0; k < text.size() && c < col; k++) {
|
for (size_t k = 0; k < text.size() && c < col; k++) {
|
||||||
|
|
@ -29,3 +36,5 @@ inline std::string g_source;
|
||||||
}
|
}
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]] inline void fail(int line, int col, const std::string &msg) { failAt(g_curFile, line, col, msg); }
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,13 @@ static const std::unordered_map<std::string, Tok> keywords = {
|
||||||
{"else", Tok::Else}, {"while", Tok::While}, {"for", Tok::For},
|
{"else", Tok::Else}, {"while", Tok::While}, {"for", Tok::For},
|
||||||
{"in", Tok::In}, {"break", Tok::Break}, {"continue", Tok::Continue},
|
{"in", Tok::In}, {"break", Tok::Break}, {"continue", Tok::Continue},
|
||||||
{"true", Tok::True}, {"false", Tok::False}, {"null", Tok::Null},
|
{"true", Tok::True}, {"false", Tok::False}, {"null", Tok::Null},
|
||||||
{"import", Tok::Import}, {"link", Tok::Link},
|
{"import", Tok::Import}, {"link", Tok::Link}, {"struct", Tok::Struct},
|
||||||
|
{"defer", Tok::Defer},
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Token> lex(const std::string &src, const std::string &file) {
|
std::vector<Token> lex(int file) {
|
||||||
g_file = file;
|
g_curFile = file;
|
||||||
g_source = src;
|
const std::string &src = g_files[file].text;
|
||||||
std::vector<Token> out;
|
std::vector<Token> out;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
int line = 1, col = 1;
|
int line = 1, col = 1;
|
||||||
|
|
@ -126,10 +127,7 @@ std::vector<Token> lex(const std::string &src, const std::string &file) {
|
||||||
case '!': n == '=' ? two(Tok::NotEq) : one(Tok::Not); break;
|
case '!': n == '=' ? two(Tok::NotEq) : one(Tok::Not); break;
|
||||||
case '<': n == '=' ? two(Tok::LessEq) : n == '<' ? two(Tok::Shl) : one(Tok::Less); break;
|
case '<': n == '=' ? two(Tok::LessEq) : n == '<' ? two(Tok::Shl) : one(Tok::Less); break;
|
||||||
case '>': n == '=' ? two(Tok::GreaterEq) : n == '>' ? two(Tok::Shr) : one(Tok::Greater); break;
|
case '>': n == '=' ? two(Tok::GreaterEq) : n == '>' ? two(Tok::Shr) : one(Tok::Greater); break;
|
||||||
case ':':
|
case ':': n == '=' ? two(Tok::Declare) : one(Tok::Colon); break;
|
||||||
if (n != '=') fail(line, col, "unexpected ':' (did you mean ':='?)");
|
|
||||||
two(Tok::Declare);
|
|
||||||
break;
|
|
||||||
case '&': n == '&' ? two(Tok::And) : one(Tok::Amp); break;
|
case '&': n == '&' ? two(Tok::And) : one(Tok::Amp); break;
|
||||||
case '|': n == '|' ? two(Tok::Or) : one(Tok::Pipe); break;
|
case '|': n == '|' ? two(Tok::Or) : one(Tok::Pipe); break;
|
||||||
default: {
|
default: {
|
||||||
|
|
@ -169,6 +167,9 @@ const char *tokName(Tok t) {
|
||||||
case Tok::Null: return "'null'";
|
case Tok::Null: return "'null'";
|
||||||
case Tok::Import: return "'import'";
|
case Tok::Import: return "'import'";
|
||||||
case Tok::Link: return "'link'";
|
case Tok::Link: return "'link'";
|
||||||
|
case Tok::Struct: return "'struct'";
|
||||||
|
case Tok::Defer: return "'defer'";
|
||||||
|
case Tok::Colon: return "':'";
|
||||||
case Tok::LParen: return "'('";
|
case Tok::LParen: return "'('";
|
||||||
case Tok::RParen: return "')'";
|
case Tok::RParen: return "')'";
|
||||||
case Tok::LBrace: return "'{'";
|
case Tok::LBrace: return "'{'";
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ enum class Tok {
|
||||||
Ident, Int, Float, String, Char,
|
Ident, Int, Float, String, Char,
|
||||||
|
|
||||||
// keywords
|
// keywords
|
||||||
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link,
|
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link, Struct, Defer,
|
||||||
|
|
||||||
// punctuation
|
// punctuation
|
||||||
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Dot, DotDot, Arrow,
|
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Colon, Dot, DotDot, Arrow,
|
||||||
|
|
||||||
// operators
|
// operators
|
||||||
Plus, Minus, Star, Slash, Percent,
|
Plus, Minus, Star, Slash, Percent,
|
||||||
|
|
@ -30,5 +30,6 @@ struct Token {
|
||||||
bool newlineBefore; // token starts a new line: ends the previous statement
|
bool newlineBefore; // token starts a new line: ends the previous statement
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Token> lex(const std::string &src, const std::string &file);
|
// Lexes g_files[file].text.
|
||||||
|
std::vector<Token> lex(int file);
|
||||||
const char *tokName(Tok t);
|
const char *tokName(Tok t);
|
||||||
|
|
|
||||||
179
src/main.cpp
179
src/main.cpp
|
|
@ -1,14 +1,16 @@
|
||||||
#include "cimport.h"
|
#include "cimport.h"
|
||||||
#include "codegen.h"
|
#include "codegen.h"
|
||||||
|
#include "error.h"
|
||||||
#include "lexer.h"
|
#include "lexer.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
|
||||||
|
#include <llvm/Config/llvm-config.h>
|
||||||
#include <llvm/IR/LegacyPassManager.h>
|
#include <llvm/IR/LegacyPassManager.h>
|
||||||
#include <llvm/MC/TargetRegistry.h>
|
#include <llvm/MC/TargetRegistry.h>
|
||||||
#include <llvm/Passes/PassBuilder.h>
|
#include <llvm/Passes/PassBuilder.h>
|
||||||
#include <llvm/Support/FileSystem.h>
|
#include <llvm/Support/FileSystem.h>
|
||||||
|
#include <llvm/Support/Path.h>
|
||||||
#include <llvm/Support/TargetSelect.h>
|
#include <llvm/Support/TargetSelect.h>
|
||||||
#include <llvm/Config/llvm-config.h>
|
|
||||||
#include <llvm/Target/TargetMachine.h>
|
#include <llvm/Target/TargetMachine.h>
|
||||||
#include <llvm/TargetParser/Host.h>
|
#include <llvm/TargetParser/Host.h>
|
||||||
|
|
||||||
|
|
@ -18,37 +20,89 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rt_source.inc" // kRuntimeSource: runtime/finch_rt.c, embedded at build time
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
static void usage() {
|
static void usage() {
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"Finch " FINCH_VERSION " - small, quick, sharp.\n\n"
|
"Finch " FINCH_VERSION " - small, quick, sharp.\n\n"
|
||||||
"usage:\n"
|
"usage:\n"
|
||||||
" finch run <file.fn> compile and run\n"
|
" finch run <file.fn> [args...] compile and run\n"
|
||||||
" finch build <file.fn> [-o name] compile to a program\n"
|
" finch build <file.fn> [-o name] compile to a program\n"
|
||||||
|
" finch ir <file.fn> show the generated LLVM IR\n"
|
||||||
|
" finch version show the version\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||||
" -O0 skip optimizations (to read the raw IR)\n"
|
" -g add debug info (for gdb / lldb)\n"
|
||||||
" finch ir <file.fn> show the generated LLVM IR\n"
|
" -O0 skip optimizations (to read the raw IR)\n");
|
||||||
" finch version show the version\n");
|
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string readFile(const std::string &path) {
|
static bool readFile(const std::string &path, std::string &out) {
|
||||||
std::ifstream in(path);
|
std::ifstream in(path);
|
||||||
if (!in) {
|
if (!in) return false;
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << in.rdbuf();
|
||||||
|
out = ss.str();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string dirOf(const std::string &path) {
|
||||||
|
size_t slash = path.find_last_of('/');
|
||||||
|
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- loading the program and its modules ----------
|
||||||
|
|
||||||
|
struct Loader {
|
||||||
|
std::vector<Program> progs;
|
||||||
|
std::map<std::string, bool> loaded; // module name -> seen
|
||||||
|
|
||||||
|
void load(const std::string &path, const std::string &module, Pos from) {
|
||||||
|
std::string text;
|
||||||
|
if (!readFile(path, text)) {
|
||||||
|
if (from.line) failAt(from.file, from.line, from.col, "can't find the module '" + module + "' (looked for " + path + ")");
|
||||||
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
|
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
std::stringstream ss;
|
int idx = (int)g_files.size();
|
||||||
ss << in.rdbuf();
|
g_files.push_back({path, text});
|
||||||
return ss.str();
|
Program p = parse(lex(idx), idx);
|
||||||
|
p.module = module;
|
||||||
|
p.path = path;
|
||||||
|
std::vector<Import> mods;
|
||||||
|
for (const Import &im : p.imports)
|
||||||
|
if (!im.isC) mods.push_back(im);
|
||||||
|
progs.push_back(std::move(p));
|
||||||
|
for (const Import &im : mods) {
|
||||||
|
if (im.path == "main") failAt(im.pos.file, im.pos.line, im.pos.col, "'main' can't be imported");
|
||||||
|
if (loaded[im.path]) continue;
|
||||||
|
loaded[im.path] = true;
|
||||||
|
load(findModule(dirOf(path), im.path), im.path, im.pos);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// next to the importing file, then in $FINCH_PATH folders
|
||||||
|
static std::string findModule(const std::string &dir, const std::string &name) {
|
||||||
|
std::string here = dir + "/" + name + ".fn";
|
||||||
|
if (sys::fs::exists(here)) return here;
|
||||||
|
if (const char *fp = std::getenv("FINCH_PATH")) {
|
||||||
|
std::stringstream ss(fp);
|
||||||
|
for (std::string d; std::getline(ss, d, ':');)
|
||||||
|
if (!d.empty() && sys::fs::exists(d + "/" + name + ".fn")) return d + "/" + name + ".fn";
|
||||||
|
}
|
||||||
|
return here;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- LLVM ----------
|
||||||
|
|
||||||
static void optimize(Module &mod, TargetMachine *tm) {
|
static void optimize(Module &mod, TargetMachine *tm) {
|
||||||
LoopAnalysisManager lam;
|
LoopAnalysisManager lam;
|
||||||
|
|
@ -93,6 +147,8 @@ static void emitObject(Module &mod, TargetMachine *tm, const std::string &path)
|
||||||
pm.run(mod);
|
pm.run(mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- linking ----------
|
||||||
|
|
||||||
// Run a shell command; returns its exit status, everything it printed goes to `output`.
|
// Run a shell command; returns its exit status, everything it printed goes to `output`.
|
||||||
static int capture(const std::string &cmd, std::string &output) {
|
static int capture(const std::string &cmd, std::string &output) {
|
||||||
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
||||||
|
|
@ -104,6 +160,38 @@ static int capture(const std::string &cmd, std::string &output) {
|
||||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string ccName() {
|
||||||
|
const char *cc = std::getenv("CC");
|
||||||
|
return cc ? cc : "cc";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The runtime is compiled once per version and kept in ~/.cache/finch.
|
||||||
|
static std::string runtimeObject() {
|
||||||
|
std::string dir;
|
||||||
|
if (const char *x = std::getenv("XDG_CACHE_HOME")) dir = std::string(x) + "/finch";
|
||||||
|
else if (const char *h = std::getenv("HOME")) dir = std::string(h) + "/.cache/finch";
|
||||||
|
else dir = "/tmp/finch-cache";
|
||||||
|
size_t hash = std::hash<std::string>()(std::string(kRuntimeSource) + FINCH_VERSION + ccName());
|
||||||
|
std::string obj = dir + "/rt-" + std::to_string(hash) + ".o";
|
||||||
|
if (sys::fs::exists(obj)) return obj;
|
||||||
|
|
||||||
|
sys::fs::create_directories(dir);
|
||||||
|
std::string src = obj + ".c", part = obj + ".part";
|
||||||
|
{
|
||||||
|
std::ofstream out(src);
|
||||||
|
out << kRuntimeSource;
|
||||||
|
}
|
||||||
|
std::string output;
|
||||||
|
int status = capture(ccName() + " -O2 -fPIC -c '" + src + "' -o '" + part + "'", output);
|
||||||
|
std::remove(src.c_str());
|
||||||
|
if (status != 0) {
|
||||||
|
std::fprintf(stderr, "%serror: couldn't build the Finch runtime (is a C compiler installed?)\n", output.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
sys::fs::rename(part, obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
// pkg-config knows the right paths for a library if it has one; otherwise plain -l<name>.
|
// pkg-config knows the right paths for a library if it has one; otherwise plain -l<name>.
|
||||||
static std::string libFlags(const std::string &lib) {
|
static std::string libFlags(const std::string &lib) {
|
||||||
std::string out;
|
std::string out;
|
||||||
|
|
@ -151,7 +239,6 @@ static std::string guessLib(const std::string &header) {
|
||||||
for (const std::string &lib : missingLibs)
|
for (const std::string &lib : missingLibs)
|
||||||
std::fprintf(stderr, "error: the library '%s' wasn't found. Is it installed? (on Nix: add it to shell.nix)\n", lib.c_str());
|
std::fprintf(stderr, "error: the library '%s' wasn't found. Is it installed? (on Nix: add it to shell.nix)\n", lib.c_str());
|
||||||
|
|
||||||
// group the missing functions by the header they came from
|
|
||||||
std::vector<std::pair<std::string, std::vector<std::string>>> byHeader;
|
std::vector<std::pair<std::string, std::vector<std::string>>> byHeader;
|
||||||
for (const std::string &name : missing) {
|
for (const std::string &name : missing) {
|
||||||
auto f = c.fns.find(name);
|
auto f = c.fns.find(name);
|
||||||
|
|
@ -178,14 +265,34 @@ static std::string guessLib(const std::string &header) {
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool endsWith(const std::string &s, const char *x) {
|
||||||
|
size_t n = std::strlen(x);
|
||||||
|
return s.size() > n && s.compare(s.size() - n, n, x) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
|
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
|
||||||
const CImports &c) {
|
const CImports &c) {
|
||||||
const char *cc = std::getenv("CC");
|
std::string cmd = ccName() + " '" + obj + "' '" + runtimeObject() + "' -o '" + exe + "' -lm";
|
||||||
std::string cmd = std::string(cc ? cc : "cc") + " '" + obj + "' -o '" + exe + "' -lm";
|
std::vector<std::string> temps;
|
||||||
for (const std::string &l : libs) cmd += libFlags(l);
|
for (const std::string &l : libs) {
|
||||||
|
if (endsWith(l, ".c")) { // compile the user's C file
|
||||||
|
std::string o = obj + "." + std::to_string(temps.size()) + ".o", output;
|
||||||
|
if (capture(ccName() + " -O2 -fPIC -c '" + l + "' -o '" + o + "'", output) != 0) {
|
||||||
|
std::fprintf(stderr, "%serror: couldn't compile %s\n", output.c_str(), l.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
temps.push_back(o);
|
||||||
|
cmd += " '" + o + "'";
|
||||||
|
} else if (endsWith(l, ".o") || endsWith(l, ".a")) {
|
||||||
|
cmd += " '" + l + "'";
|
||||||
|
} else {
|
||||||
|
cmd += libFlags(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
std::string output;
|
std::string output;
|
||||||
int status = capture(cmd, output);
|
int status = capture(cmd, output);
|
||||||
std::remove(obj.c_str());
|
std::remove(obj.c_str());
|
||||||
|
for (const std::string &t : temps) std::remove(t.c_str());
|
||||||
if (status == 127) {
|
if (status == 127) {
|
||||||
std::fprintf(stderr, "error: no C compiler found to link with (install gcc or clang, or set CC)\n");
|
std::fprintf(stderr, "error: no C compiler found to link with (install gcc or clang, or set CC)\n");
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
|
|
@ -201,6 +308,12 @@ static std::string stem(const std::string &path) {
|
||||||
return dot == std::string::npos ? name : name.substr(0, dot);
|
return dot == std::string::npos ? name : name.substr(0, dot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string shellQuote(const std::string &s) {
|
||||||
|
std::string out = "'";
|
||||||
|
for (char ch : s) out += ch == '\'' ? std::string("'\\''") : std::string(1, ch);
|
||||||
|
return out + "'";
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc == 2 && (std::string(argv[1]) == "version" || std::string(argv[1]) == "--version")) {
|
if (argc == 2 && (std::string(argv[1]) == "version" || std::string(argv[1]) == "--version")) {
|
||||||
std::printf("finch %s (LLVM %s)\n", FINCH_VERSION, LLVM_VERSION_STRING);
|
std::printf("finch %s (LLVM %s)\n", FINCH_VERSION, LLVM_VERSION_STRING);
|
||||||
|
|
@ -208,26 +321,40 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
if (argc < 3) usage();
|
if (argc < 3) usage();
|
||||||
std::string cmd = argv[1], file = argv[2];
|
std::string cmd = argv[1], file = argv[2];
|
||||||
|
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
||||||
std::string out = stem(file);
|
std::string out = stem(file);
|
||||||
std::vector<std::string> libs;
|
std::vector<std::string> libs, programArgs;
|
||||||
bool optimizeCode = true;
|
bool optimizeCode = true, debug = false;
|
||||||
for (int i = 3; i < argc; i++) {
|
for (int i = 3; i < argc; i++) {
|
||||||
std::string a = argv[i];
|
std::string a = argv[i];
|
||||||
if (a == "-o" && i + 1 < argc) out = argv[++i];
|
if (cmd == "run" && !programArgs.empty()) programArgs.push_back(a);
|
||||||
|
else if (a == "-o" && i + 1 < argc && cmd == "build") out = argv[++i];
|
||||||
else if (a == "-O0") optimizeCode = false;
|
else if (a == "-O0") optimizeCode = false;
|
||||||
|
else if (a == "-g") debug = true;
|
||||||
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
|
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
|
||||||
else if (a.rfind("-l", 0) == 0 && a.size() > 2) libs.push_back(a.substr(2));
|
else if (a.rfind("-l", 0) == 0 && a.size() > 2) libs.push_back(a.substr(2));
|
||||||
|
else if (cmd == "run") programArgs.push_back(a);
|
||||||
else usage();
|
else usage();
|
||||||
}
|
}
|
||||||
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
|
||||||
|
|
||||||
Program prog = parse(lex(readFile(file), file));
|
Loader loader;
|
||||||
for (const Link &l : prog.links) libs.push_back(l.lib);
|
loader.load(file, "", Pos{});
|
||||||
size_t slash = file.find_last_of('/');
|
std::vector<Import> allImports;
|
||||||
CImports c = importHeaders(prog.imports, slash == std::string::npos ? "." : file.substr(0, slash));
|
std::vector<std::string> dirs;
|
||||||
|
for (const Program &p : loader.progs) {
|
||||||
|
allImports.insert(allImports.end(), p.imports.begin(), p.imports.end());
|
||||||
|
for (const Link &l : p.links) {
|
||||||
|
bool file = endsWith(l.lib, ".c") || endsWith(l.lib, ".o") || endsWith(l.lib, ".a");
|
||||||
|
std::string path = file && l.lib[0] != '/' ? dirOf(p.path) + "/" + l.lib : l.lib;
|
||||||
|
if (file && !sys::fs::exists(path)) failAt(l.pos.file, l.pos.line, l.pos.col, "can't find the file '" + path + "'");
|
||||||
|
libs.push_back(path);
|
||||||
|
}
|
||||||
|
if (std::find(dirs.begin(), dirs.end(), dirOf(p.path)) == dirs.end()) dirs.push_back(dirOf(p.path));
|
||||||
|
}
|
||||||
|
CImports c = importHeaders(allImports, dirs);
|
||||||
LLVMContext ctx;
|
LLVMContext ctx;
|
||||||
TargetMachine *tm = hostMachine();
|
TargetMachine *tm = hostMachine();
|
||||||
std::unique_ptr<Module> mod = generate(prog, c, ctx, *tm);
|
std::unique_ptr<Module> mod = generate(loader.progs, c, ctx, *tm, debug);
|
||||||
if (optimizeCode) optimize(*mod, tm);
|
if (optimizeCode) optimize(*mod, tm);
|
||||||
|
|
||||||
if (cmd == "ir") {
|
if (cmd == "ir") {
|
||||||
|
|
@ -246,7 +373,9 @@ int main(int argc, char **argv) {
|
||||||
link(obj, out, libs, c);
|
link(obj, out, libs, c);
|
||||||
|
|
||||||
if (cmd == "run") {
|
if (cmd == "run") {
|
||||||
int status = std::system(("'" + out + "'").c_str());
|
std::string line = shellQuote(out);
|
||||||
|
for (const std::string &a : programArgs) line += " " + shellQuote(a);
|
||||||
|
int status = std::system(line.c_str());
|
||||||
std::remove(out.c_str());
|
std::remove(out.c_str());
|
||||||
if (WIFSIGNALED(status)) {
|
if (WIFSIGNALED(status)) {
|
||||||
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));
|
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));
|
||||||
|
|
|
||||||
185
src/parser.cpp
185
src/parser.cpp
|
|
@ -5,6 +5,7 @@
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
bool typeFromName(const std::string &name, Type &out) {
|
bool typeFromName(const std::string &name, Type &out) {
|
||||||
|
|
@ -29,28 +30,32 @@ bool isTypeName(const std::string &name) {
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
explicit Parser(const std::vector<Token> &t) : toks(t) {}
|
Parser(const std::vector<Token> &t, int f) : toks(t), file(f) {}
|
||||||
|
|
||||||
Program program() {
|
Program program() {
|
||||||
Program p;
|
Program p;
|
||||||
while (!at(Tok::End)) {
|
while (!at(Tok::End)) {
|
||||||
if (at(Tok::Import)) p.imports.push_back(import());
|
switch (cur().kind) {
|
||||||
else if (at(Tok::Link)) p.links.push_back(link());
|
case Tok::Import: p.imports.push_back(import()); break;
|
||||||
else p.fns.push_back(function());
|
case Tok::Link: p.links.push_back(link()); break;
|
||||||
|
case Tok::Struct: p.structs.push_back(structDecl()); break;
|
||||||
|
default: p.fns.push_back(function()); break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::vector<Token> &toks;
|
const std::vector<Token> &toks;
|
||||||
|
int file;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
int parenDepth = 0; // inside ( ) newlines don't end anything
|
int parenDepth = 0; // inside ( ) and [ ] newlines don't end anything
|
||||||
|
|
||||||
const Token &cur() const { return toks[i]; }
|
const Token &cur() const { return toks[i]; }
|
||||||
const Token &peekTok(size_t off = 1) const { return toks[std::min(i + off, toks.size() - 1)]; }
|
const Token &peekTok(size_t off = 1) const { return toks[std::min(i + off, toks.size() - 1)]; }
|
||||||
const Token &next() { return toks[i++]; }
|
const Token &next() { return toks[i++]; }
|
||||||
bool at(Tok k) const { return cur().kind == k; }
|
bool at(Tok k) const { return cur().kind == k; }
|
||||||
Pos pos() const { return {cur().line, cur().col}; }
|
Pos pos() const { return {cur().line, cur().col, file}; }
|
||||||
|
|
||||||
std::string describe(const Token &t) const {
|
std::string describe(const Token &t) const {
|
||||||
return t.kind == Tok::Ident ? "'" + t.text + "'" : tokName(t.kind);
|
return t.kind == Tok::Ident ? "'" + t.text + "'" : tokName(t.kind);
|
||||||
|
|
@ -71,27 +76,44 @@ private:
|
||||||
fail(cur().line, cur().col, "unexpected " + describe(cur()) + " " + ctx);
|
fail(cur().line, cur().col, "unexpected " + describe(cur()) + " " + ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A token can continue an expression only if it is on the same line (or we're inside parens).
|
// A token can continue an expression only if it is on the same line (or we're inside brackets).
|
||||||
bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
|
bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
|
||||||
|
|
||||||
// int, u8, ptr, ptr[int], ptr[ptr[u8]] ...
|
// int, u8, ptr, ptr[int], []str, Point, math.Vec ...
|
||||||
Type type() {
|
Type type() {
|
||||||
if (!at(Tok::Ident) || !isTypeName(cur().text))
|
if (accept(Tok::LBracket)) {
|
||||||
unexpected("(expected a type like int, float, u8, str or ptr[int])");
|
expect(Tok::RBracket, "']' (an array type looks like []int)");
|
||||||
|
return Type::arrayOf(type());
|
||||||
|
}
|
||||||
|
if (!at(Tok::Ident)) unexpected("(expected a type like int, str, []int, ptr[int] or a struct name)");
|
||||||
|
std::string name = next().text;
|
||||||
Type t;
|
Type t;
|
||||||
typeFromName(next().text, t);
|
if (typeFromName(name, t)) {
|
||||||
if (t.kind == Type::Ptr && accept(Tok::LBracket)) {
|
if (t.kind == Type::Ptr && accept(Tok::LBracket)) {
|
||||||
t = Type::ptrTo(type());
|
t = Type::ptrTo(type());
|
||||||
expect(Tok::RBracket);
|
expect(Tok::RBracket);
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
t = Type(Type::Named);
|
||||||
|
t.name = name;
|
||||||
|
if (at(Tok::Dot) && peekTok().kind == Tok::Ident) { // module.Struct
|
||||||
|
next();
|
||||||
|
t.module = name;
|
||||||
|
t.name = next().text;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
// Does a declaration like `int x` or `ptr[int] p` start here?
|
// Does a declaration like `int x`, `ptr[int] p`, `[]int a`, `Point p` or `math.Vec v` start here?
|
||||||
bool atDeclaration() const {
|
bool atDeclaration() const {
|
||||||
if (!at(Tok::Ident) || !isTypeName(cur().text)) return false;
|
if (at(Tok::LBracket)) return peekTok().kind == Tok::RBracket;
|
||||||
|
if (!at(Tok::Ident)) return false;
|
||||||
Tok n = peekTok().kind;
|
Tok n = peekTok().kind;
|
||||||
return n == Tok::Ident || (cur().text == "ptr" && n == Tok::LBracket);
|
if (n == Tok::Ident) return !peekTok().newlineBefore;
|
||||||
|
if (cur().text == "ptr" && n == Tok::LBracket) return true;
|
||||||
|
return n == Tok::Dot && peekTok(2).kind == Tok::Ident && peekTok(3).kind == Tok::Ident &&
|
||||||
|
!peekTok(3).newlineBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- top level ----------
|
// ---------- top level ----------
|
||||||
|
|
@ -100,9 +122,12 @@ private:
|
||||||
Import im;
|
Import im;
|
||||||
im.pos = pos();
|
im.pos = pos();
|
||||||
expect(Tok::Import);
|
expect(Tok::Import);
|
||||||
if (at(Tok::Ident))
|
if (at(Tok::Ident)) {
|
||||||
fail(im.pos.line, im.pos.col, "Finch modules are not ready yet; for C headers write import \"stdio.h\"");
|
im.isC = false;
|
||||||
im.path = expect(Tok::String, "a header name in quotes, like \"stdio.h\"").text;
|
im.path = next().text;
|
||||||
|
} else {
|
||||||
|
im.path = expect(Tok::String, "a module name (import math) or a C header in quotes (import \"stdio.h\")").text;
|
||||||
|
}
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,17 +136,44 @@ private:
|
||||||
l.pos = pos();
|
l.pos = pos();
|
||||||
expect(Tok::Link);
|
expect(Tok::Link);
|
||||||
l.lib = expect(Tok::String, "a library name in quotes, like link \"glfw\"").text;
|
l.lib = expect(Tok::String, "a library name in quotes, like link \"glfw\"").text;
|
||||||
|
auto ends = [&](const char *x) { size_t n = std::strlen(x); return l.lib.size() > n && l.lib.compare(l.lib.size() - n, n, x) == 0; };
|
||||||
|
if (ends(".c") || ends(".o") || ends(".a")) return l; // your own C code, next to the .fn file
|
||||||
if (l.lib.rfind("lib", 0) == 0 || l.lib.find(".so") != std::string::npos || l.lib.find('/') != std::string::npos)
|
if (l.lib.rfind("lib", 0) == 0 || l.lib.find(".so") != std::string::npos || l.lib.find('/') != std::string::npos)
|
||||||
fail(l.pos.line, l.pos.col, "write just the library's name: for libglfw.so that's link \"glfw\"");
|
fail(l.pos.line, l.pos.col, "write just the library's name: for libglfw.so that's link \"glfw\"");
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StructDecl structDecl() {
|
||||||
|
StructDecl s;
|
||||||
|
s.pos = pos();
|
||||||
|
expect(Tok::Struct);
|
||||||
|
s.name = expect(Tok::Ident, "a struct name").text;
|
||||||
|
if (isTypeName(s.name)) fail(s.pos.line, s.pos.col, "'" + s.name + "' is a built-in type name, pick another");
|
||||||
|
Pos open = pos();
|
||||||
|
expect(Tok::LBrace);
|
||||||
|
while (!at(Tok::RBrace)) {
|
||||||
|
if (at(Tok::End)) fail(open.line, open.col, "this '{' is never closed (missing '}')");
|
||||||
|
Field f;
|
||||||
|
f.pos = pos();
|
||||||
|
if (!atDeclaration()) unexpected("(a field looks like: int x or str name = \"default\")");
|
||||||
|
f.type = type();
|
||||||
|
f.name = expect(Tok::Ident, "a field name").text;
|
||||||
|
for (const Field &other : s.fields)
|
||||||
|
if (other.name == f.name) fail(f.pos.line, f.pos.col, "the field '" + f.name + "' is listed twice");
|
||||||
|
if (accept(Tok::Assign)) f.init = expr();
|
||||||
|
s.fields.push_back(std::move(f));
|
||||||
|
endOfStatement();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
FnDecl function() {
|
FnDecl function() {
|
||||||
FnDecl fn;
|
FnDecl fn;
|
||||||
fn.pos = pos();
|
fn.pos = pos();
|
||||||
if (at(Tok::Ident) && isTypeName(cur().text))
|
if (at(Tok::Ident) && isTypeName(cur().text))
|
||||||
fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }");
|
fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }");
|
||||||
expect(Tok::Fn, "'fn' (every function starts with fn)");
|
expect(Tok::Fn, "'fn', 'struct', 'import' or 'link' (every function starts with fn)");
|
||||||
|
|
||||||
fn.name = expect(Tok::Ident, "a function name").text;
|
fn.name = expect(Tok::Ident, "a function name").text;
|
||||||
expect(Tok::LParen);
|
expect(Tok::LParen);
|
||||||
|
|
@ -177,15 +229,23 @@ private:
|
||||||
}
|
}
|
||||||
case Tok::For: {
|
case Tok::For: {
|
||||||
next();
|
next();
|
||||||
auto s = std::make_unique<ForStmt>(p);
|
std::string var = expect(Tok::Ident, "a loop variable name").text;
|
||||||
s->var = expect(Tok::Ident, "a loop variable name").text;
|
|
||||||
expect(Tok::In);
|
expect(Tok::In);
|
||||||
s->from = expr();
|
ExprPtr first = expr();
|
||||||
expect(Tok::DotDot, "'..' (like 'for i in 0..10')");
|
if (accept(Tok::DotDot)) { // for i in 0..10
|
||||||
|
auto s = std::make_unique<ForStmt>(p);
|
||||||
|
s->var = var;
|
||||||
|
s->from = std::move(first);
|
||||||
s->to = expr();
|
s->to = expr();
|
||||||
s->body = block();
|
s->body = block();
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
auto s = std::make_unique<ForEachStmt>(p); // for x in list
|
||||||
|
s->var = var;
|
||||||
|
s->list = std::move(first);
|
||||||
|
s->body = block();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
case Tok::Return: {
|
case Tok::Return: {
|
||||||
next();
|
next();
|
||||||
auto s = std::make_unique<ReturnStmt>(p);
|
auto s = std::make_unique<ReturnStmt>(p);
|
||||||
|
|
@ -194,19 +254,25 @@ private:
|
||||||
}
|
}
|
||||||
case Tok::Break: next(); return std::make_unique<BreakStmt>(p);
|
case Tok::Break: next(); return std::make_unique<BreakStmt>(p);
|
||||||
case Tok::Continue: next(); return std::make_unique<ContinueStmt>(p);
|
case Tok::Continue: next(); return std::make_unique<ContinueStmt>(p);
|
||||||
|
case Tok::Defer: {
|
||||||
|
next();
|
||||||
|
if (at(Tok::Defer)) fail(p.line, p.col, "defer inside defer is not allowed");
|
||||||
|
return std::make_unique<DeferStmt>(p, statement());
|
||||||
|
}
|
||||||
case Tok::LBrace: return block();
|
case Tok::LBrace: return block();
|
||||||
case Tok::Fn: fail(p.line, p.col, "functions can't be inside other functions (move it out)");
|
case Tok::Fn: fail(p.line, p.col, "functions can't be inside other functions (move it out)");
|
||||||
|
case Tok::Struct: fail(p.line, p.col, "structs go outside functions, at the top level of the file");
|
||||||
case Tok::Import:
|
case Tok::Import:
|
||||||
case Tok::Link: fail(p.line, p.col, "import and link go at the top of the file, outside functions");
|
case Tok::Link: fail(p.line, p.col, "import and link go at the top of the file, outside functions");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// int x = 5
|
// int x = 5, Point p, []int a
|
||||||
if (atDeclaration()) {
|
if (atDeclaration()) {
|
||||||
auto s = std::make_unique<VarDeclStmt>(p);
|
auto s = std::make_unique<VarDeclStmt>(p);
|
||||||
s->hasType = true;
|
s->hasType = true;
|
||||||
s->type = type();
|
s->type = type();
|
||||||
s->name = next().text;
|
s->name = expect(Tok::Ident, "a variable name").text;
|
||||||
if (accept(Tok::Assign)) s->init = expr();
|
if (accept(Tok::Assign)) s->init = expr();
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +289,7 @@ private:
|
||||||
|
|
||||||
ExprPtr e = expr();
|
ExprPtr e = expr();
|
||||||
|
|
||||||
// x = 5, p.value += 1, ...
|
// x = 5, p.value += 1, a[i] = 2, ...
|
||||||
char op = 0;
|
char op = 0;
|
||||||
switch (cur().kind) {
|
switch (cur().kind) {
|
||||||
case Tok::Assign: op = '='; break;
|
case Tok::Assign: op = '='; break;
|
||||||
|
|
@ -237,8 +303,8 @@ private:
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if (op && sameLine()) {
|
if (op && sameLine()) {
|
||||||
if (e->kind != ExprKind::Var && e->kind != ExprKind::Member)
|
if (e->kind != ExprKind::Var && e->kind != ExprKind::Member && e->kind != ExprKind::Index)
|
||||||
fail(p.line, p.col, "only a variable or p.value can be assigned to");
|
fail(p.line, p.col, "only a variable, a field, an element or p.value can be assigned to");
|
||||||
auto s = std::make_unique<AssignStmt>(p);
|
auto s = std::make_unique<AssignStmt>(p);
|
||||||
s->target = std::move(e);
|
s->target = std::move(e);
|
||||||
s->op = op;
|
s->op = op;
|
||||||
|
|
@ -247,7 +313,8 @@ private:
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e->kind != ExprKind::Call) fail(p.line, p.col, "this value is computed but never used");
|
if (e->kind != ExprKind::Call && e->kind != ExprKind::Method)
|
||||||
|
fail(p.line, p.col, "this value is computed but never used");
|
||||||
return std::make_unique<ExprStmt>(p, std::move(e));
|
return std::make_unique<ExprStmt>(p, std::move(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,29 +422,51 @@ private:
|
||||||
return postfix();
|
return postfix();
|
||||||
}
|
}
|
||||||
|
|
||||||
// p.value
|
// p.value, a.len, a[i], a.push(x), math.sqrt(2)
|
||||||
ExprPtr postfix() {
|
ExprPtr postfix() {
|
||||||
ExprPtr e = primary();
|
ExprPtr e = primary();
|
||||||
while (sameLine() && at(Tok::Dot)) {
|
while (sameLine()) {
|
||||||
Pos p = pos();
|
Pos p = pos();
|
||||||
|
if (accept(Tok::Dot)) {
|
||||||
|
std::string name = expect(Tok::Ident, "a name after '.'").text;
|
||||||
|
if (at(Tok::LParen) && !cur().newlineBefore) {
|
||||||
|
auto m = std::make_unique<MethodExpr>(p, std::move(e), name);
|
||||||
|
args(m->args, m->argNames);
|
||||||
|
e = std::move(m);
|
||||||
|
} else {
|
||||||
|
e = std::make_unique<MemberExpr>(p, std::move(e), name);
|
||||||
|
}
|
||||||
|
} else if (at(Tok::LBracket)) {
|
||||||
next();
|
next();
|
||||||
e = std::make_unique<MemberExpr>(p, std::move(e), expect(Tok::Ident, "a name after '.'").text);
|
parenDepth++;
|
||||||
|
ExprPtr index = expr();
|
||||||
|
parenDepth--;
|
||||||
|
expect(Tok::RBracket);
|
||||||
|
e = std::make_unique<IndexExpr>(p, std::move(e), std::move(index));
|
||||||
|
} else {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// name(arg, arg, ...) -- we are at the '('
|
// ( arg, name: arg, ... ) -- we are at the '('
|
||||||
ExprPtr callArgs(Pos p, const std::string &name) {
|
void args(std::vector<ExprPtr> &out, std::vector<std::string> &names) {
|
||||||
auto call = std::make_unique<CallExpr>(p, name);
|
|
||||||
expect(Tok::LParen);
|
expect(Tok::LParen);
|
||||||
parenDepth++;
|
parenDepth++;
|
||||||
if (!at(Tok::RParen)) {
|
if (!at(Tok::RParen)) {
|
||||||
do call->args.push_back(expr());
|
do {
|
||||||
while (accept(Tok::Comma));
|
std::string name;
|
||||||
|
if (at(Tok::Ident) && peekTok().kind == Tok::Colon) {
|
||||||
|
name = next().text;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
names.push_back(name);
|
||||||
|
out.push_back(expr());
|
||||||
|
} while (accept(Tok::Comma));
|
||||||
}
|
}
|
||||||
parenDepth--;
|
parenDepth--;
|
||||||
expect(Tok::RParen);
|
expect(Tok::RParen);
|
||||||
return call;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprPtr primary() {
|
ExprPtr primary() {
|
||||||
|
|
@ -406,9 +495,25 @@ private:
|
||||||
expect(Tok::RParen);
|
expect(Tok::RParen);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
case Tok::LBracket: { // [1, 2, 3]
|
||||||
|
next();
|
||||||
|
auto a = std::make_unique<ArrayLitExpr>(p);
|
||||||
|
parenDepth++;
|
||||||
|
while (!at(Tok::RBracket)) {
|
||||||
|
a->elems.push_back(expr());
|
||||||
|
if (!accept(Tok::Comma)) break; // a trailing comma is fine
|
||||||
|
}
|
||||||
|
parenDepth--;
|
||||||
|
expect(Tok::RBracket);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
case Tok::Ident: {
|
case Tok::Ident: {
|
||||||
next();
|
next();
|
||||||
if (at(Tok::LParen) && !cur().newlineBefore) return callArgs(p, t.text);
|
if (at(Tok::LParen) && !cur().newlineBefore) {
|
||||||
|
auto call = std::make_unique<CallExpr>(p, t.text);
|
||||||
|
args(call->args, call->argNames);
|
||||||
|
return call;
|
||||||
|
}
|
||||||
if (isTypeName(t.text))
|
if (isTypeName(t.text))
|
||||||
fail(p.line, p.col, "a type name alone is not a value (did you mean " + t.text + "(...)?)");
|
fail(p.line, p.col, "a type name alone is not a value (did you mean " + t.text + "(...)?)");
|
||||||
return std::make_unique<VarExpr>(p, t.text);
|
return std::make_unique<VarExpr>(p, t.text);
|
||||||
|
|
@ -420,6 +525,6 @@ private:
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Program parse(const std::vector<Token> &tokens) {
|
Program parse(const std::vector<Token> &tokens, int file) {
|
||||||
return Parser(tokens).program();
|
return Parser(tokens, file).program();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
#include "ast.h"
|
#include "ast.h"
|
||||||
#include "lexer.h"
|
#include "lexer.h"
|
||||||
|
|
||||||
Program parse(const std::vector<Token> &tokens);
|
// Parses one file's tokens; `file` is its index in g_files.
|
||||||
|
Program parse(const std::vector<Token> &tokens, int file);
|
||||||
|
|
|
||||||
4
tests/fail/array_compare.fn
Normal file
4
tests/fail/array_compare.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: compare the parts one by one
|
||||||
|
fn main() {
|
||||||
|
print([1] == [1])
|
||||||
|
}
|
||||||
4
tests/fail/bad_int.fn
Normal file
4
tests/fail/bad_int.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: can't turn "12x" into int
|
||||||
|
fn main() {
|
||||||
|
print(int("12x"))
|
||||||
|
}
|
||||||
4
tests/fail/defer_return.fn
Normal file
4
tests/fail/defer_return.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: return can't be used inside defer
|
||||||
|
fn main() {
|
||||||
|
defer return
|
||||||
|
}
|
||||||
6
tests/fail/each_readonly.fn
Normal file
6
tests/fail/each_readonly.fn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: the loop variable 'x' can't be changed
|
||||||
|
fn main() {
|
||||||
|
for x in [1, 2] {
|
||||||
|
x = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
4
tests/fail/empty_array.fn
Normal file
4
tests/fail/empty_array.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: an empty [] needs a type
|
||||||
|
fn main() {
|
||||||
|
a := []
|
||||||
|
}
|
||||||
5
tests/fail/free_value.fn
Normal file
5
tests/fail/free_value.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: arrays, str and structs are freed by Finch
|
||||||
|
fn main() {
|
||||||
|
x := 5
|
||||||
|
free(x)
|
||||||
|
}
|
||||||
5
tests/fail/index_range.fn
Normal file
5
tests/fail/index_range.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: runtime error: index 5 is out of range (the length is 3)
|
||||||
|
fn main() {
|
||||||
|
a := [1, 2, 3]
|
||||||
|
print(a[5])
|
||||||
|
}
|
||||||
3
tests/fail/module_missing.fn
Normal file
3
tests/fail/module_missing.fn
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
// expect: can't find the module 'nothere'
|
||||||
|
import nothere
|
||||||
|
fn main() {}
|
||||||
4
tests/fail/module_not_imported.fn
Normal file
4
tests/fail/module_not_imported.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: to use a module, add import geometry
|
||||||
|
fn main() {
|
||||||
|
v := geometry.Vec(1, 2)
|
||||||
|
}
|
||||||
5
tests/fail/pop_empty.fn
Normal file
5
tests/fail/pop_empty.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: runtime error: pop() on an empty array
|
||||||
|
fn main() {
|
||||||
|
[]int a
|
||||||
|
a.pop()
|
||||||
|
}
|
||||||
5
tests/fail/push_type.fn
Normal file
5
tests/fail/push_type.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: the element must be int, but this is str
|
||||||
|
fn main() {
|
||||||
|
a := [1, 2]
|
||||||
|
a.push("x")
|
||||||
|
}
|
||||||
6
tests/fail/str_null.fn
Normal file
6
tests/fail/str_null.fn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: a str is never null
|
||||||
|
fn main() {
|
||||||
|
s := "a"
|
||||||
|
if s == null {
|
||||||
|
}
|
||||||
|
}
|
||||||
4
tests/fail/str_plus_int.fn
Normal file
4
tests/fail/str_plus_int.fn
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: turn the other side into text first: str(...)
|
||||||
|
fn main() {
|
||||||
|
s := "a" + 5
|
||||||
|
}
|
||||||
8
tests/fail/struct_count.fn
Normal file
8
tests/fail/struct_count.fn
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// expect: P has 2 fields, but got 1 values
|
||||||
|
struct P {
|
||||||
|
int x
|
||||||
|
int y
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
p := P(1)
|
||||||
|
}
|
||||||
8
tests/fail/struct_no_field.fn
Normal file
8
tests/fail/struct_no_field.fn
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// expect: the struct P has no field 'y' (it has: x)
|
||||||
|
struct P {
|
||||||
|
int x
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
p := P(1)
|
||||||
|
print(p.y)
|
||||||
|
}
|
||||||
5
tests/fail/struct_self.fn
Normal file
5
tests/fail/struct_self.fn
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: contains itself; use ptr[A] or []A
|
||||||
|
struct A {
|
||||||
|
A inner
|
||||||
|
}
|
||||||
|
fn main() {}
|
||||||
28
tests/run.sh
28
tests/run.sh
|
|
@ -1,27 +1,45 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Finch test suite.
|
# Finch test suite.
|
||||||
# tests/run/*.fn must compile and print exactly what is in the matching .out file
|
# tests/run/*.fn must compile and print exactly what is in the matching .out file
|
||||||
|
# (files without `fn main` are modules or helpers and are skipped;
|
||||||
|
# a matching .in file is fed to the program as its input)
|
||||||
# tests/fail/*.fn must fail; the first line is "// expect: <text that must appear in the error>"
|
# tests/fail/*.fn must fail; the first line is "// expect: <text that must appear in the error>"
|
||||||
|
# MEMCHECK=1 tests/run.sh also runs every program under valgrind: no leaks, no bad memory access
|
||||||
set -u
|
set -u
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
FINCH=${FINCH:-../build/finch}
|
FINCH=${FINCH:-../build/finch}
|
||||||
pass=0; failed=0
|
pass=0; failed=0
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
for f in run/*.fn; do
|
for f in run/*.fn; do
|
||||||
|
grep -q "^fn main" "$f" || continue
|
||||||
want="${f%.fn}.out"
|
want="${f%.fn}.out"
|
||||||
got=$("$FINCH" run "$f" 2>&1)
|
input="${f%.fn}.in"
|
||||||
if [ "$got" == "$(cat "$want")" ]; then
|
[ -f "$input" ] || input=/dev/null
|
||||||
pass=$((pass + 1))
|
got=$("$FINCH" run "$f" < "$input" 2>&1)
|
||||||
else
|
if [ "$got" != "$(cat "$want")" ]; then
|
||||||
failed=$((failed + 1))
|
failed=$((failed + 1))
|
||||||
echo "FAIL $f"
|
echo "FAIL $f"
|
||||||
diff <(echo "$got") "$want" | head -20
|
diff <(echo "$got") "$want" | head -20
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
|
if [ "${MEMCHECK:-0}" = 1 ]; then
|
||||||
|
exe="$tmp/$(basename "${f%.fn}")"
|
||||||
|
"$FINCH" build "$f" -o "$exe" >/dev/null 2>&1
|
||||||
|
if ! valgrind -q --leak-check=full --errors-for-leak-kinds=all --error-exitcode=99 "$exe" < "$input" >/dev/null 2>"$tmp/vg"; then
|
||||||
|
failed=$((failed + 1))
|
||||||
|
echo "MEMCHECK FAIL $f"
|
||||||
|
head -20 "$tmp/vg"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
pass=$((pass + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
for f in fail/*.fn; do
|
for f in fail/*.fn; do
|
||||||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||||
got=$("$FINCH" run "$f" 2>&1)
|
got=$("$FINCH" run "$f" < /dev/null 2>&1)
|
||||||
status=$?
|
status=$?
|
||||||
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
|
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
|
||||||
pass=$((pass + 1))
|
pass=$((pass + 1))
|
||||||
|
|
|
||||||
17
tests/run/abi.c
Normal file
17
tests/run/abi.c
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#include "abi.h"
|
||||||
|
#include <string.h>
|
||||||
|
Vec2 vec2_add(Vec2 a, Vec2 b) { return (Vec2){a.x + b.x, a.y + b.y}; }
|
||||||
|
Vec3 vec3_scale(Vec3 v, float k) { return (Vec3){v.x * k, v.y * k, v.z * k}; }
|
||||||
|
Color color_invert(Color c) { return (Color){255 - c.r, 255 - c.g, 255 - c.b, c.a}; }
|
||||||
|
Mixed mixed_bump(Mixed m) { return (Mixed){m.d + 0.5, m.i + 1}; }
|
||||||
|
Pair pair_swap(Pair p) { return (Pair){p.b, p.a}; }
|
||||||
|
Big big_sum(Big a, Big b) { return (Big){a.a + b.a, a.b + b.b, a.c + b.c}; }
|
||||||
|
int64_t big_total(Big b) { return b.a + b.b + b.c; }
|
||||||
|
int named_len(Named n) { return (int)strlen(n.name) * 100 + n.count; }
|
||||||
|
Sprite sprite_move(Sprite s, float dx) { s.pos.x += dx; s.color.a = 7; return s; }
|
||||||
|
int item_sum(Item *it) { int s = 0; for (; it; it = it->next) s += it->value; return s; }
|
||||||
|
double many(Vec2 a, Vec2 b, Vec2 c, Vec2 d, Vec2 e, Vec2 f, Vec2 g, Vec2 h, Vec2 i, Pair p1, Pair p2, Pair p3, Pair p4) {
|
||||||
|
return a.x + b.x + c.x + d.x + e.x + f.x + g.x + h.x + i.x + i.y + p1.a + p2.b + p3.a + p4.b;
|
||||||
|
}
|
||||||
|
int64_t apply_pair(Pair (*fn)(Pair, int64_t), Pair p, int64_t k) { Pair r = fn(p, k); return r.a * 1000 + r.b; }
|
||||||
|
float apply_vec(Vec2 (*fn)(Vec2), Vec2 v) { Vec2 r = fn(v); return r.x + r.y; }
|
||||||
25
tests/run/abi.h
Normal file
25
tests/run/abi.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef struct { float x, y; } Vec2; // SSE: <2 x float>
|
||||||
|
typedef struct { float x, y, z; } Vec3; // SSE, SSE (float tail)
|
||||||
|
typedef struct { uint8_t r, g, b, a; } Color; // INTEGER: i32
|
||||||
|
typedef struct { double d; int i; } Mixed; // SSE + INTEGER
|
||||||
|
typedef struct { int64_t a, b; } Pair; // INTEGER, INTEGER
|
||||||
|
typedef struct { int64_t a, b, c; } Big; // MEMORY (24 bytes)
|
||||||
|
typedef struct { char name[8]; int count; } Named; // fixed array inside
|
||||||
|
typedef struct { Vec2 pos; Color color; } Sprite; // nested
|
||||||
|
typedef struct Item { int value; struct Item *next; } Item;
|
||||||
|
|
||||||
|
Vec2 vec2_add(Vec2 a, Vec2 b);
|
||||||
|
Vec3 vec3_scale(Vec3 v, float k);
|
||||||
|
Color color_invert(Color c);
|
||||||
|
Mixed mixed_bump(Mixed m);
|
||||||
|
Pair pair_swap(Pair p);
|
||||||
|
Big big_sum(Big a, Big b);
|
||||||
|
int64_t big_total(Big b);
|
||||||
|
int named_len(Named n);
|
||||||
|
Sprite sprite_move(Sprite s, float dx);
|
||||||
|
int item_sum(Item *it);
|
||||||
|
// many structs: runs out of registers, later ones go on the stack
|
||||||
|
double many(Vec2 a, Vec2 b, Vec2 c, Vec2 d, Vec2 e, Vec2 f, Vec2 g, Vec2 h, Vec2 i, Pair p1, Pair p2, Pair p3, Pair p4);
|
||||||
|
int64_t apply_pair(Pair (*fn)(Pair, int64_t), Pair p, int64_t k);
|
||||||
|
float apply_vec(Vec2 (*fn)(Vec2), Vec2 v);
|
||||||
34
tests/run/c_structs.fn
Normal file
34
tests/run/c_structs.fn
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import "abi.h"
|
||||||
|
link "abi.c"
|
||||||
|
|
||||||
|
fn pairTimes(Pair p, int k) -> Pair {
|
||||||
|
return Pair(a: p.a * k, b: p.b + k)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip(Vec2 v) -> Vec2 {
|
||||||
|
return Vec2(x: v.y, y: v.x * 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(vec2_add(Vec2(1.5, 2), Vec2(x: 0.5, y: 3)))
|
||||||
|
print(vec3_scale(Vec3(1, 2, 3), 2))
|
||||||
|
print(color_invert(Color(r: 10, g: 20, b: 30, a: 255)))
|
||||||
|
print(mixed_bump(Mixed(d: 1.25, i: 41)))
|
||||||
|
print(pair_swap(Pair(1, 2)))
|
||||||
|
big := big_sum(Big(1, 2, 3), Big(10, 20, 30))
|
||||||
|
print(big, big_total(big))
|
||||||
|
Named n
|
||||||
|
n.name[0] = 'h'
|
||||||
|
n.name[1] = 'i'
|
||||||
|
n.count = 5
|
||||||
|
print(named_len(n), n.name.len)
|
||||||
|
s := sprite_move(Sprite(pos: Vec2(1, 1), color: Color(1, 2, 3, 4)), 2.5)
|
||||||
|
print(s)
|
||||||
|
v := Vec2(1, 0)
|
||||||
|
print(many(v, v, v, v, v, v, v, v, Vec2(2, 3), Pair(1, 0), Pair(0, 2), Pair(3, 0), Pair(0, 4)))
|
||||||
|
print(apply_pair(addr(pairTimes), Pair(2, 3), 5))
|
||||||
|
print(apply_vec(addr(flip), Vec2(1, 2)))
|
||||||
|
second := Item(value: 2, next: null)
|
||||||
|
first := Item(value: 1, next: addr(second))
|
||||||
|
print(item_sum(addr(first)))
|
||||||
|
}
|
||||||
12
tests/run/c_structs.out
Normal file
12
tests/run/c_structs.out
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
Vec2(x: 2, y: 5)
|
||||||
|
Vec3(x: 2, y: 4, z: 6)
|
||||||
|
Color(r: 245, g: 235, b: 225, a: 255)
|
||||||
|
Mixed(d: 1.75, i: 42)
|
||||||
|
Pair(a: 2, b: 1)
|
||||||
|
Big(a: 11, b: 22, c: 33) 66
|
||||||
|
205 8
|
||||||
|
Sprite(pos: Vec2(x: 3.5, y: 1), color: Color(r: 1, g: 2, b: 3, a: 7))
|
||||||
|
23
|
||||||
|
10008
|
||||||
|
12
|
||||||
|
3
|
||||||
20
tests/run/defer.fn
Normal file
20
tests/run/defer.fn
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
fn work(int n) -> int {
|
||||||
|
defer print("cleanup", n)
|
||||||
|
if n > 1 {
|
||||||
|
return n * 10
|
||||||
|
}
|
||||||
|
defer print("only when small")
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(work(1))
|
||||||
|
print(work(2))
|
||||||
|
for i in 0..3 {
|
||||||
|
defer print("end of round", i)
|
||||||
|
if i == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
print("round", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
tests/run/defer.out
Normal file
10
tests/run/defer.out
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
only when small
|
||||||
|
cleanup 1
|
||||||
|
1
|
||||||
|
cleanup 2
|
||||||
|
20
|
||||||
|
round 0
|
||||||
|
end of round 0
|
||||||
|
end of round 1
|
||||||
|
round 2
|
||||||
|
end of round 2
|
||||||
9
tests/run/files.fn
Normal file
9
tests/run/files.fn
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {
|
||||||
|
path := "/tmp/finch_test_file.txt"
|
||||||
|
ok := write_file(path, "line one\nline two\n")
|
||||||
|
print(ok, file_exists(path))
|
||||||
|
text := read_file(path)
|
||||||
|
lines := text.trim().split("\n")
|
||||||
|
print(lines, lines.len)
|
||||||
|
print(file_exists("/definitely/not/here"))
|
||||||
|
}
|
||||||
3
tests/run/files.out
Normal file
3
tests/run/files.out
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
true true
|
||||||
|
["line one", "line two"] 2
|
||||||
|
false
|
||||||
18
tests/run/geometry.fn
Normal file
18
tests/run/geometry.fn
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import "math.h"
|
||||||
|
|
||||||
|
struct Vec {
|
||||||
|
float x
|
||||||
|
float y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(Vec v) -> float {
|
||||||
|
return sqrt(v.x * v.x + v.y * v.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(Vec a, Vec b) -> Vec {
|
||||||
|
return Vec(a.x + b.x, a.y + b.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(Vec v) -> str {
|
||||||
|
return "(" + str(v.x) + ", " + str(v.y) + ") of length " + str(length(v))
|
||||||
|
}
|
||||||
7
tests/run/input.fn
Normal file
7
tests/run/input.fn
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {
|
||||||
|
name := input("Name? ")
|
||||||
|
age := int(input("Age? "))
|
||||||
|
print("Hi", name, "next year you will be", age + 1)
|
||||||
|
rest := input()
|
||||||
|
print("rest:", rest, rest.len)
|
||||||
|
}
|
||||||
2
tests/run/input.in
Normal file
2
tests/run/input.in
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Ola
|
||||||
|
41
|
||||||
2
tests/run/input.out
Normal file
2
tests/run/input.out
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Name? Age? Hi Ola next year you will be 42
|
||||||
|
rest: 0
|
||||||
98
tests/run/memory.fn
Normal file
98
tests/run/memory.fn
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
struct Node {
|
||||||
|
str name
|
||||||
|
ptr[Node] next
|
||||||
|
[]str tags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(int n) -> []str {
|
||||||
|
[]str out
|
||||||
|
out.resize(n)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn firstLong([]str words) -> str {
|
||||||
|
for w in words {
|
||||||
|
tmp := w + "!"
|
||||||
|
if w.len > 3 {
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loops() -> int {
|
||||||
|
count := 0
|
||||||
|
for i in 0..10 {
|
||||||
|
s := "item " + str(i)
|
||||||
|
if i == 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count += s.len
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
t := "x" + "y"
|
||||||
|
if t.len == 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deferred() -> str {
|
||||||
|
log := "start"
|
||||||
|
defer print("deferred ran, log was:", log)
|
||||||
|
log = log + " middle"
|
||||||
|
names := ["a", "b"]
|
||||||
|
defer print("names:", names)
|
||||||
|
return log + " end"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main([]str args) {
|
||||||
|
print(args.len > 0)
|
||||||
|
print(firstLong(["hi", "hello", "hey"]))
|
||||||
|
print(firstLong(["a"]))
|
||||||
|
print(loops())
|
||||||
|
print(deferred())
|
||||||
|
|
||||||
|
s := "ab"
|
||||||
|
s = s + s
|
||||||
|
s = s
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
list := ["one", "two", "three"]
|
||||||
|
for w in list {
|
||||||
|
if w == "two" {
|
||||||
|
list.push("four")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(list)
|
||||||
|
last := list.pop()
|
||||||
|
print(last, list)
|
||||||
|
|
||||||
|
head := new(Node(name: "first", tags: ["t1"]))
|
||||||
|
head.next = new(Node(name: "second"))
|
||||||
|
print(head.name, head.next.name, head.tags)
|
||||||
|
n := head
|
||||||
|
while n != null {
|
||||||
|
print("node", n.name)
|
||||||
|
n = n.next
|
||||||
|
}
|
||||||
|
free(head.next)
|
||||||
|
free(head)
|
||||||
|
|
||||||
|
[]str words
|
||||||
|
for i in 0..3 {
|
||||||
|
words.push("w" + str(i))
|
||||||
|
}
|
||||||
|
words.clear()
|
||||||
|
words.resize(2)
|
||||||
|
print(words, words.len)
|
||||||
|
print([1, 2, 3].slice(1, 3), "abc".bytes())
|
||||||
|
x := ["keep"]
|
||||||
|
y := x
|
||||||
|
x[0] = "changed"
|
||||||
|
print(x, y)
|
||||||
|
}
|
||||||
16
tests/run/memory.out
Normal file
16
tests/run/memory.out
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
true
|
||||||
|
hello!
|
||||||
|
none
|
||||||
|
30
|
||||||
|
names: ["a", "b"]
|
||||||
|
deferred ran, log was: start middle
|
||||||
|
start middle end
|
||||||
|
abab
|
||||||
|
["one", "two", "three", "four"]
|
||||||
|
four ["one", "two", "three"]
|
||||||
|
first second ["t1"]
|
||||||
|
node first
|
||||||
|
node second
|
||||||
|
["", ""] 2
|
||||||
|
[2, 3] [97, 98, 99]
|
||||||
|
["changed"] ["keep"]
|
||||||
18
tests/run/modules.fn
Normal file
18
tests/run/modules.fn
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import geometry
|
||||||
|
import shapes
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
v := geometry.Vec(3, 4)
|
||||||
|
print(geometry.length(v))
|
||||||
|
w := geometry.add(v, geometry.Vec(x: 1, y: 1))
|
||||||
|
print(geometry.describe(w))
|
||||||
|
geometry.Vec z
|
||||||
|
print(z)
|
||||||
|
b := shapes.Box(size: geometry.Vec(2, 5))
|
||||||
|
print(shapes.area(b), b)
|
||||||
|
total := 0
|
||||||
|
for i in 0..3 {
|
||||||
|
total += i
|
||||||
|
}
|
||||||
|
print("total", total)
|
||||||
|
}
|
||||||
5
tests/run/modules.out
Normal file
5
tests/run/modules.out
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
5
|
||||||
|
(4, 5) of length 6.40312
|
||||||
|
Vec(x: 0, y: 0)
|
||||||
|
10 Box(corner: Vec(x: 0, y: 0), size: Vec(x: 2, y: 5))
|
||||||
|
total 3
|
||||||
23
tests/run/ptr_value_field.fn
Normal file
23
tests/run/ptr_value_field.fn
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
struct Node {
|
||||||
|
int value
|
||||||
|
ptr[Node] next
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Box {
|
||||||
|
int size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
last := new(Node(value: 2, next: null))
|
||||||
|
first := Node(value: 1, next: last)
|
||||||
|
p := addr(first)
|
||||||
|
print(p.value, p.next.value)
|
||||||
|
p.value = 10
|
||||||
|
print(first.value)
|
||||||
|
b := Box(3)
|
||||||
|
q := addr(b)
|
||||||
|
print(q.value, q.size)
|
||||||
|
q.value = Box(7)
|
||||||
|
print(b)
|
||||||
|
free(last)
|
||||||
|
}
|
||||||
4
tests/run/ptr_value_field.out
Normal file
4
tests/run/ptr_value_field.out
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
1 2
|
||||||
|
10
|
||||||
|
Box(size: 3) 3
|
||||||
|
Box(size: 7)
|
||||||
10
tests/run/shapes.fn
Normal file
10
tests/run/shapes.fn
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import geometry
|
||||||
|
|
||||||
|
struct Box {
|
||||||
|
geometry.Vec corner
|
||||||
|
geometry.Vec size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(Box b) -> float {
|
||||||
|
return b.size.x * b.size.y
|
||||||
|
}
|
||||||
78
tests/run/structs_arrays_strings.fn
Normal file
78
tests/run/structs_arrays_strings.fn
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
struct Point {
|
||||||
|
int x
|
||||||
|
int y
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Player {
|
||||||
|
str name = "nobody"
|
||||||
|
Point pos
|
||||||
|
[]int scores
|
||||||
|
int lives = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moved(Point p, int dx) -> Point {
|
||||||
|
return Point(x: p.x + dx, y: p.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total([]int xs) -> int {
|
||||||
|
sum := 0
|
||||||
|
for x in xs {
|
||||||
|
sum += x
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addScore(Player p, int s) -> Player {
|
||||||
|
p.scores.push(s)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
p := Point(1, 2)
|
||||||
|
print(p, p.x, p.y)
|
||||||
|
q := moved(p, 10)
|
||||||
|
print(q)
|
||||||
|
|
||||||
|
pl := Player(name: "Ola")
|
||||||
|
print(pl)
|
||||||
|
pl.scores.push(10)
|
||||||
|
pl.scores.push(20)
|
||||||
|
pl.pos.x = 5
|
||||||
|
print(pl.name, pl.lives, pl.scores, pl.pos)
|
||||||
|
|
||||||
|
copy := pl
|
||||||
|
copy.name = "Tom"
|
||||||
|
copy.scores.push(30)
|
||||||
|
print(pl.name, pl.scores.len, copy.name, copy.scores.len)
|
||||||
|
|
||||||
|
pl2 := addScore(pl, 99)
|
||||||
|
print(pl.scores, pl2.scores)
|
||||||
|
|
||||||
|
nums := [5, 3, 8, 1]
|
||||||
|
nums.sort()
|
||||||
|
print(nums, total(nums), nums.len)
|
||||||
|
print(nums.pop(), nums)
|
||||||
|
nums.insert(0, 42)
|
||||||
|
nums.remove(1)
|
||||||
|
print(nums, nums.contains(8), nums.find(3))
|
||||||
|
|
||||||
|
names := ["b", "a", "c"]
|
||||||
|
names.sort()
|
||||||
|
print(names, names.join("-"))
|
||||||
|
|
||||||
|
s := "Hello"
|
||||||
|
s += ", Finch"
|
||||||
|
print(s, s.len, s[0], s.upper(), s.sub(0, 5))
|
||||||
|
words := "a,b,,c".split(",")
|
||||||
|
print(words, words.len)
|
||||||
|
print(" trim ".trim(), "x".repeat(3), "abc".replace("b", "BB"), "hello".contains("ell"))
|
||||||
|
print(str(42) + "!", str(3.5), str(true), int("123") + 1, float("2.5") * 2)
|
||||||
|
s[0] = 'J'
|
||||||
|
print(s)
|
||||||
|
for c in "hi" {
|
||||||
|
print(c)
|
||||||
|
}
|
||||||
|
grid := [[1, 2], [3, 4]]
|
||||||
|
grid[1].push(5)
|
||||||
|
print(grid, grid[1][2])
|
||||||
|
}
|
||||||
18
tests/run/structs_arrays_strings.out
Normal file
18
tests/run/structs_arrays_strings.out
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
Point(x: 1, y: 2) 1 2
|
||||||
|
Point(x: 11, y: 2)
|
||||||
|
Player(name: "Ola", pos: Point(x: 0, y: 0), scores: [], lives: 3)
|
||||||
|
Ola 3 [10, 20] Point(x: 5, y: 0)
|
||||||
|
Ola 2 Tom 3
|
||||||
|
[10, 20] [10, 20, 99]
|
||||||
|
[1, 3, 5, 8] 17 4
|
||||||
|
8 [1, 3, 5]
|
||||||
|
[42, 3, 5] false 1
|
||||||
|
["a", "b", "c"] a-b-c
|
||||||
|
Hello, Finch 12 H HELLO, FINCH Hello
|
||||||
|
["a", "b", "", "c"] 4
|
||||||
|
trim xxx aBBc true
|
||||||
|
42! 3.5 true 124 5
|
||||||
|
Jello, Finch
|
||||||
|
h
|
||||||
|
i
|
||||||
|
[[1, 2], [3, 4, 5]] 5
|
||||||
Loading…
Reference in a new issue