mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
finch.toml, threads and atomics, finch fmt, C++ files, struct #defines, a struct variable given for its ptr
- finch.toml: name, entry, paths, include, links, libdirs, cflags, ldflags, and [target.windows] etc.;
finch run / build / test with no file use its entry
- import thread: spawn, join, mutex, cpus; atomic_add/sub/load/store/swap/cas built in
- finch fmt [--check]: indentation by { }, trailing spaces, empty lines; also as LSP formatting
- link "x.cpp" (or in finch.toml): built with the C++ compiler, which then links (FINCH_CXX)
- #define RAYWHITE (Color){ 245, 245, 245, 255 } imports as a Color
- push(body) where push takes ptr[Body]: the same as push(addr(body))
- --target android (NDK clang from ANDROID_NDK_HOME, -llog)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
parent
c830c780fe
commit
d428498efd
40 changed files with 1398 additions and 18 deletions
|
|
@ -20,9 +20,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -881,6 +885,114 @@ const char *finch_fs_data_home(void) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
// ---------- threads (std/thread.fch, through std/finch_thread.h) ----------
|
||||
|
||||
typedef struct {
|
||||
void (*fn)(void *);
|
||||
void *arg;
|
||||
} ThreadStart;
|
||||
|
||||
#ifdef _WIN32
|
||||
static DWORD WINAPI thread_main(LPVOID p) {
|
||||
ThreadStart st = *(ThreadStart *)p;
|
||||
free(p);
|
||||
st.fn(st.arg);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static void *thread_main(void *p) {
|
||||
ThreadStart st = *(ThreadStart *)p;
|
||||
free(p);
|
||||
st.fn(st.arg);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// run fn(arg) on a new thread; NULL if it couldn't start
|
||||
void *finch_thread_spawn(void (*fn)(void *), void *arg) {
|
||||
ThreadStart *st = finch_alloc((int64_t)sizeof(ThreadStart));
|
||||
st->fn = fn;
|
||||
st->arg = arg;
|
||||
#ifdef _WIN32
|
||||
HANDLE h = CreateThread(NULL, 0, thread_main, st, 0, NULL);
|
||||
if (!h) {
|
||||
free(st);
|
||||
return NULL;
|
||||
}
|
||||
return h;
|
||||
#else
|
||||
pthread_t *t = finch_alloc((int64_t)sizeof(pthread_t));
|
||||
if (pthread_create(t, NULL, thread_main, st) != 0) {
|
||||
free(st);
|
||||
free(t);
|
||||
return NULL;
|
||||
}
|
||||
return t;
|
||||
#endif
|
||||
}
|
||||
|
||||
// wait for the thread to end; 1 if that worked
|
||||
int finch_thread_join(void *t) {
|
||||
if (!t) return 0;
|
||||
#ifdef _WIN32
|
||||
int ok = WaitForSingleObject((HANDLE)t, INFINITE) == WAIT_OBJECT_0;
|
||||
CloseHandle((HANDLE)t);
|
||||
return ok;
|
||||
#else
|
||||
int ok = pthread_join(*(pthread_t *)t, NULL) == 0;
|
||||
free(t);
|
||||
return ok;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *finch_mutex_new(void) {
|
||||
#ifdef _WIN32
|
||||
CRITICAL_SECTION *m = finch_alloc((int64_t)sizeof(CRITICAL_SECTION));
|
||||
InitializeCriticalSection(m);
|
||||
#else
|
||||
pthread_mutex_t *m = finch_alloc((int64_t)sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(m, NULL);
|
||||
#endif
|
||||
return m;
|
||||
}
|
||||
|
||||
void finch_mutex_lock(void *m) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection((CRITICAL_SECTION *)m);
|
||||
#else
|
||||
pthread_mutex_lock((pthread_mutex_t *)m);
|
||||
#endif
|
||||
}
|
||||
|
||||
void finch_mutex_unlock(void *m) {
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection((CRITICAL_SECTION *)m);
|
||||
#else
|
||||
pthread_mutex_unlock((pthread_mutex_t *)m);
|
||||
#endif
|
||||
}
|
||||
|
||||
void finch_mutex_free(void *m) {
|
||||
if (!m) return;
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection((CRITICAL_SECTION *)m);
|
||||
#else
|
||||
pthread_mutex_destroy((pthread_mutex_t *)m);
|
||||
#endif
|
||||
free(m);
|
||||
}
|
||||
|
||||
int finch_cpu_count(void) {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return (int)si.dwNumberOfProcessors;
|
||||
#else
|
||||
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
return n > 0 ? (int)n : 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------- maps ----------
|
||||
// map[K]V = { entries; len; used; cap; index; icap; adds }
|
||||
// entries: `used` slots of es bytes, each { uint64 hash; K key; V value }, in insertion order.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,18 @@ struct CConst { // enum values and #define constants
|
|||
str text // a float as written, so no digits are lost: 3.14159265358979323846
|
||||
bool isStr // #define NAME "text"
|
||||
str s
|
||||
str structName // #define RAYWHITE (Color){ 245, 245, 245, 255 }: the struct, and its fields in order
|
||||
[]str fields // each as a Finch literal: 245, 0.5
|
||||
[]bool fieldFloat
|
||||
}
|
||||
|
||||
// what visitMacroVar looks for inside (Color){ ... }
|
||||
struct InitFind {
|
||||
bool found
|
||||
CXCursor list
|
||||
bool ok
|
||||
[]str fields
|
||||
[]bool floats
|
||||
}
|
||||
|
||||
struct CField {
|
||||
|
|
@ -381,7 +393,7 @@ fn macroTokens(ptr[Importer] imp, CXCursor c) -> MacroTokens {
|
|||
u32 n = 0
|
||||
clang_tokenize(imp.tu, clang_getCursorExtent(c), addr(tokens), addr(n))
|
||||
out := MacroTokens(safe: true)
|
||||
allowed := ["(", ")", "+", "-", "*", "/", "%", "<<", ">>", "|", "&", "^", "~", "!", "<", ">", "<=", ">=", "==", "!=", "&&", "||", "?", ":", ","]
|
||||
allowed := ["(", ")", "+", "-", "*", "/", "%", "<<", ">>", "|", "&", "^", "~", "!", "<", ">", "<=", ">=", "==", "!=", "&&", "||", "?", ":", ",", "{", "}"]
|
||||
words := ["sizeof", "unsigned", "signed", "int", "long", "short", "char", "float", "double", "const", "_Bool"]
|
||||
depth := 0
|
||||
for k in 1..int(n) { // token 0 is the macro's name
|
||||
|
|
@ -678,6 +690,72 @@ fn evalMacros(ptr[Importer] imp, ptr index, Header h, []ptr[char] argv) {
|
|||
clang_disposeTranslationUnit(tu)
|
||||
}
|
||||
|
||||
// a float as a Finch literal: 2.0, not 2
|
||||
fn floatText(float v) -> str {
|
||||
t := str(v)
|
||||
if !t.contains(".") && !t.contains("e") {
|
||||
t = t + ".0"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// (Color){ 245, 245, 245, 255 }: a struct whose fields are all numbers, in order
|
||||
fn structMacro(ptr[Importer] imp, str name, CXCursor c, CXType t) {
|
||||
sname := recordName(imp, t)
|
||||
if !imp.out.structs.has(sname) {
|
||||
return
|
||||
}
|
||||
find := InitFind(ok: true)
|
||||
clang_visitChildren(c, addr(visitFindInit), addr(find))
|
||||
if !find.found {
|
||||
return
|
||||
}
|
||||
clang_visitChildren(find.list, addr(visitInitField), addr(find))
|
||||
if !find.ok || find.fields.len == 0 || find.fields.len > imp.out.structs[sname].fields.len {
|
||||
return
|
||||
}
|
||||
imp.out.consts[name] = CConst(structName: sname, fields: find.fields, fieldFloat: find.floats)
|
||||
}
|
||||
|
||||
fn visitFindInit(CXCursor c, CXCursor parent, ptr data) -> i32 {
|
||||
ptr[InitFind] f = data
|
||||
if clang_getCursorKind(c) == CXCursor_InitListExpr {
|
||||
f.found = true
|
||||
f.list = c
|
||||
return CXChildVisit_Break
|
||||
}
|
||||
return CXChildVisit_Recurse
|
||||
}
|
||||
|
||||
fn visitInitField(CXCursor c, CXCursor parent, ptr data) -> i32 {
|
||||
ptr[InitFind] f = data
|
||||
r := clang_Cursor_Evaluate(c)
|
||||
if r == null {
|
||||
f.ok = false
|
||||
return CXChildVisit_Break
|
||||
}
|
||||
k := clang_EvalResult_getKind(r)
|
||||
if k == CXEval_Int {
|
||||
f.fields.push(str(int(clang_EvalResult_getAsLongLong(r))))
|
||||
f.floats.push(false)
|
||||
} else if k == CXEval_Float {
|
||||
v := clang_EvalResult_getAsDouble(r)
|
||||
if v == v && v - v == 0.0 {
|
||||
f.fields.push(floatText(v))
|
||||
f.floats.push(true)
|
||||
} else {
|
||||
f.ok = false
|
||||
}
|
||||
} else {
|
||||
f.ok = false // a struct in the struct, a pointer, a text: not a simple constant
|
||||
}
|
||||
clang_EvalResult_dispose(r)
|
||||
if !f.ok {
|
||||
return CXChildVisit_Break
|
||||
}
|
||||
return CXChildVisit_Continue
|
||||
}
|
||||
|
||||
fn visitMacroVar(CXCursor c, CXCursor parent, ptr data) -> i32 {
|
||||
ptr[Importer] imp = data
|
||||
if clang_getCursorKind(c) != CXCursor_VarDecl {
|
||||
|
|
@ -688,8 +766,16 @@ fn visitMacroVar(CXCursor c, CXCursor parent, ptr data) -> i32 {
|
|||
return CXChildVisit_Continue
|
||||
}
|
||||
name = name.sub(10, name.len)
|
||||
if imp.out.consts.has(name) {
|
||||
return CXChildVisit_Continue
|
||||
}
|
||||
t := clang_getCanonicalType(clang_getCursorType(c))
|
||||
if t.kind == CXType_Record {
|
||||
structMacro(imp, name, c, t)
|
||||
return CXChildVisit_Continue
|
||||
}
|
||||
r := clang_Cursor_Evaluate(c)
|
||||
if r == null || imp.out.consts.has(name) {
|
||||
if r == null {
|
||||
return CXChildVisit_Continue
|
||||
}
|
||||
k := clang_EvalResult_getKind(r)
|
||||
|
|
@ -698,7 +784,7 @@ fn visitMacroVar(CXCursor c, CXCursor parent, ptr data) -> i32 {
|
|||
} else if k == CXEval_Float {
|
||||
v := clang_EvalResult_getAsDouble(r)
|
||||
if v == v && v - v == 0.0 { // not nan or inf
|
||||
imp.out.consts[name] = CConst(isFloat: true, f: v, text: str(v))
|
||||
imp.out.consts[name] = CConst(isFloat: true, f: v, text: floatText(v))
|
||||
}
|
||||
} else if k == CXEval_StrLiteral {
|
||||
imp.out.consts[name] = CConst(isStr: true, s: clang_EvalResult_getAsStr(r))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ fn builtins() -> []Doc {
|
|||
doc("max", "max(a, b, ...) -> number", "The biggest of the numbers, in the type they all fit."),
|
||||
doc("abs", "abs(x) -> number", "x without its minus sign (any number type)."),
|
||||
doc("clamp", "clamp(x, lo, hi) -> number", "x, but at least lo and at most hi."),
|
||||
doc("atomic_add", "atomic_add(ptr[int] p, n) -> int", "Adds n to the number at p in one step other threads can't split; gives back what was there before."),
|
||||
doc("atomic_sub", "atomic_sub(ptr[int] p, n) -> int", "Takes n from the number at p in one step; gives back what was there before."),
|
||||
doc("atomic_load", "atomic_load(ptr[int] p) -> int", "Reads the number at p, as another thread last wrote it."),
|
||||
doc("atomic_store", "atomic_store(ptr[int] p, n)", "Writes n to p so other threads see it whole."),
|
||||
doc("atomic_swap", "atomic_swap(ptr[int] p, n) -> int", "Puts n at p and gives back what was there, in one step."),
|
||||
doc("atomic_cas", "atomic_cas(ptr[int] p, old, new) -> bool", "Puts new at p only if old is there (compare and swap); true if it did."),
|
||||
doc("addr", "addr(x) -> ptr[T]", "A pointer to a variable, field or element. addr(function) gives C a callback."),
|
||||
doc("new", "new(value) -> ptr[T]", "Puts a copy of the value on the heap. Give it back with free(...)."),
|
||||
doc("free", "free(ptr p)", "Gives back memory from new(...) (or from C), freeing what the value owns."),
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
224
src/fmt.fch
Normal file
224
src/fmt.fch
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// finch fmt: the indentation by { }, 4 spaces a level; no spaces at line ends; at most one empty line
|
||||
// in a row; one line end at the end of the file. Nothing else is moved: lines that go on inside ( or [,
|
||||
// """ text and /* */ comments keep their shape, shifted along with the line they belong to.
|
||||
|
||||
struct Scan {
|
||||
int braces // { } open at the start of the line
|
||||
int parens // ( [ open
|
||||
bool inLong // inside """ text
|
||||
bool inBlock // inside /* */
|
||||
char last // the last thing of code on the line ('"' for text): + or , means the next line goes on
|
||||
}
|
||||
|
||||
// how deep a line's leading spaces go (a tab is 4)
|
||||
fn indentOf(str line) -> int {
|
||||
n := 0
|
||||
for c in line {
|
||||
if c == ' ' {
|
||||
n += 1
|
||||
} else if c == '\t' {
|
||||
n += 4
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// how many spaces and tabs a line starts with
|
||||
fn leading(str line) -> int {
|
||||
i := 0
|
||||
while i < line.len && (line[i] == ' ' || line[i] == '\t') {
|
||||
i += 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
fn trimEnd(str line) -> str {
|
||||
n := line.len
|
||||
while n > 0 && (line[n - 1] == ' ' || line[n - 1] == '\t' || line[n - 1] == '\r') {
|
||||
n -= 1
|
||||
}
|
||||
return line.sub(0, n)
|
||||
}
|
||||
|
||||
fn spaces(int n) -> str {
|
||||
[]str out
|
||||
for i in 0..n {
|
||||
out.push(" ")
|
||||
}
|
||||
return out.join("")
|
||||
}
|
||||
|
||||
fn startsWith(str line, int i, str what) -> bool {
|
||||
return i + what.len <= line.len && line.sub(i, i + what.len) == what
|
||||
}
|
||||
|
||||
// past one line: what is open at its end
|
||||
fn scanLine(str line, ptr[Scan] s) {
|
||||
s.last = ' '
|
||||
i := 0
|
||||
while i < line.len {
|
||||
c := line[i]
|
||||
if s.inLong {
|
||||
if startsWith(line, i, "\"\"\"") {
|
||||
s.inLong = false
|
||||
i += 3
|
||||
} else {
|
||||
i += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s.inBlock {
|
||||
if startsWith(line, i, "*/") {
|
||||
s.inBlock = false
|
||||
i += 2
|
||||
} else {
|
||||
i += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if startsWith(line, i, "//") {
|
||||
return
|
||||
}
|
||||
if startsWith(line, i, "/*") {
|
||||
s.inBlock = true
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if startsWith(line, i, "\"\"\"") {
|
||||
s.inLong = true
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
if c == '"' {
|
||||
i = pastString(line, i + 1)
|
||||
s.last = '"'
|
||||
continue
|
||||
}
|
||||
if c == '\'' {
|
||||
i += 1
|
||||
while i < line.len && line[i] != '\'' {
|
||||
if line[i] == '\\' {
|
||||
i += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
i += 1
|
||||
s.last = '"'
|
||||
continue
|
||||
}
|
||||
if c != ' ' && c != '\t' && c != '\r' {
|
||||
s.last = c
|
||||
}
|
||||
if c == '{' {
|
||||
s.braces += 1
|
||||
} else if c == '}' {
|
||||
s.braces -= 1
|
||||
} else if c == '(' || c == '[' {
|
||||
s.parens += 1
|
||||
} else if c == ')' || c == ']' {
|
||||
s.parens -= 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
// past "text" (i is just after the opening quote), with \(code) that may have its own "text" in it
|
||||
fn pastString(str line, int start) -> int {
|
||||
i := start
|
||||
while i < line.len {
|
||||
c := line[i]
|
||||
if c == '"' {
|
||||
return i + 1
|
||||
}
|
||||
if c == '\\' && i + 1 < line.len && line[i + 1] == '(' {
|
||||
depth := 1
|
||||
i += 2
|
||||
while i < line.len && depth > 0 {
|
||||
if line[i] == '"' {
|
||||
i = pastString(line, i + 1)
|
||||
continue
|
||||
}
|
||||
if line[i] == '(' {
|
||||
depth += 1
|
||||
} else if line[i] == ')' {
|
||||
depth -= 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c == '\\' {
|
||||
i += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
fn format(str text) -> str {
|
||||
lines := text.replace("\r\n", "\n").split("\n")
|
||||
[]str out
|
||||
s := Scan()
|
||||
stmtWas := 0 // where the line that started the statement was, and where it is now
|
||||
stmtNow := 0
|
||||
blank := 0
|
||||
for raw in lines {
|
||||
goesOn := s.last == '+' || s.last == '-' || s.last == '*' || s.last == '/' || s.last == ',' || s.last == '=' || s.last == '|' || s.last == '&' || s.last == '%'
|
||||
own := !s.inLong && !s.inBlock && s.parens <= 0 && !goesOn // the line starts something of its own
|
||||
if s.inLong {
|
||||
// """ text as it is, moved with its statement (its closing line says how much is cut off)
|
||||
out.push(shifted(raw, stmtNow - stmtWas))
|
||||
scanLine(raw, addr(s))
|
||||
continue
|
||||
}
|
||||
line := trimEnd(raw)
|
||||
if line.len == 0 {
|
||||
if s.inBlock {
|
||||
out.push("")
|
||||
} else {
|
||||
blank += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if blank > 0 && out.len > 0 {
|
||||
out.push("")
|
||||
}
|
||||
blank = 0
|
||||
body := line.sub(leading(line), line.len)
|
||||
if own {
|
||||
depth := s.braces
|
||||
if body.starts_with("}") {
|
||||
depth -= 1
|
||||
}
|
||||
if depth < 0 {
|
||||
depth = 0
|
||||
}
|
||||
stmtWas = indentOf(line)
|
||||
stmtNow = depth * 4
|
||||
out.push(spaces(stmtNow) + body)
|
||||
} else {
|
||||
out.push(shifted(line, stmtNow - stmtWas))
|
||||
}
|
||||
scanLine(line, addr(s))
|
||||
}
|
||||
while out.len > 0 && out[out.len - 1].len == 0 {
|
||||
out.pop()
|
||||
}
|
||||
return out.join("\n") + "\n"
|
||||
}
|
||||
|
||||
// the line moved right (or left) by `by`, keeping what it has after its indentation
|
||||
fn shifted(str line, int by) -> str {
|
||||
n := indentOf(line)
|
||||
body := line.sub(leading(line), line.len)
|
||||
if body.len == 0 {
|
||||
return line
|
||||
}
|
||||
to := n + by
|
||||
if to < 0 {
|
||||
to = 0
|
||||
}
|
||||
return spaces(to) + body
|
||||
}
|
||||
84
src/gen.fch
84
src/gen.fch
|
|
@ -3458,6 +3458,19 @@ fn orElse(ptr[Gen] g, ast.Node e, bool discard) -> Val {
|
|||
return Val(v: op(g, "phi " + llty(g, v.ty) + " [ " + fv + ", %" + badEnd + " ], [ " + v.v + ", %" + ok + " ]"), ty: v.ty, fresh: owning(g, v.ty))
|
||||
}
|
||||
|
||||
// An argument: a struct variable given where a ptr to that struct goes is passed by its address,
|
||||
// as if it were addr(x) (worldDef for b2CreateWorld(const b2WorldDef*), body for move(ptr[Body] b))
|
||||
fn argWant(ptr[Gen] g, ast.Node a, ast.Type want) -> Val {
|
||||
if a.kind == "var" && want.kind == "ptr" && want.elem.len == 1 && want.elem[0].kind == "struct" {
|
||||
id := lookup(g, a.name)
|
||||
if id >= 0 && ast.same(varAt(g, id).ty, want.elem[0]) {
|
||||
pl := place(g, a, true)
|
||||
return Val(v: pl.addr, ty: ast.ptrTo(pl.ty))
|
||||
}
|
||||
}
|
||||
return exprWant(g, a, want)
|
||||
}
|
||||
|
||||
fn exprWant(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
||||
if e.kind == "array" {
|
||||
return arrayLit(g, e, want)
|
||||
|
|
@ -3900,6 +3913,21 @@ fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref {
|
|||
id := lookup(g, e.name)
|
||||
if id < 0 && g.ci.consts.has(e.name) {
|
||||
c := g.ci.consts[e.name]
|
||||
if c.structName.len > 0 {
|
||||
// RAYWHITE: Color(245, 245, 245, 255)
|
||||
si := findStruct(g, "C", c.structName)
|
||||
[]ast.Node args
|
||||
[]str names
|
||||
for i in 0..c.fields.len {
|
||||
kind := "int"
|
||||
if c.fieldFloat[i] {
|
||||
kind = "float"
|
||||
}
|
||||
args.push(ast.Node(kind: kind, text: c.fields[i], line: e.line, col: e.col, file: e.file))
|
||||
names.push(g.structs[si].fnames[i]) // by name: C leaves the fields not given at 0
|
||||
}
|
||||
return Ref(isPlace: false, val: construct(g, si, args, names, e))
|
||||
}
|
||||
if c.isStr {
|
||||
return Ref(isPlace: false, val: strConst(g, c.s))
|
||||
}
|
||||
|
|
@ -4986,7 +5014,7 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
|||
if i < f.params.len {
|
||||
pt := ptypes[i]
|
||||
a := pl.params[i]
|
||||
v := coerce(g, exprWant(g, c.kids[i], pt), pt, c.kids[i], "argument " + str(i + 1) + " of " + f.name)
|
||||
v := coerce(g, argWant(g, c.kids[i], pt), pt, c.kids[i], "argument " + str(i + 1) + " of " + f.name)
|
||||
temps.push(v)
|
||||
hold(g, v)
|
||||
if a.kind == "memory" {
|
||||
|
|
@ -5391,6 +5419,9 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
if n == "assert" {
|
||||
return assertCall(g, c)
|
||||
}
|
||||
if ["atomic_add", "atomic_sub", "atomic_load", "atomic_store", "atomic_swap", "atomic_cas"].contains(n) {
|
||||
return atomicCall(g, c)
|
||||
}
|
||||
if lookup(g, n) >= 0 {
|
||||
fail(g, c, "'" + n + "' is a variable, not a function")
|
||||
}
|
||||
|
|
@ -5502,6 +5533,55 @@ fn mathCall(ptr[Gen] g, ast.Node c) -> Val {
|
|||
return out
|
||||
}
|
||||
|
||||
fn atomicAlign(ptr[Gen] g, ast.Type t) -> str {
|
||||
return str(bitsOf(t) / 8)
|
||||
}
|
||||
|
||||
// atomic_add(p, n) and the rest: one step on a whole number that other threads see all at once.
|
||||
// p is a ptr[int] (or any whole number type) to it; add, sub and swap give back what was there before,
|
||||
// cas(p, old, new) puts new there only if old is there, and says whether it did.
|
||||
fn atomicCall(ptr[Gen] g, ast.Node c) -> Val {
|
||||
n := c.name
|
||||
if g.index {
|
||||
noteRef(g, c, c.col, n.len, docs.hover(docs.find(docs.builtins(), n)), "", 0, 0)
|
||||
}
|
||||
want := 2
|
||||
if n == "atomic_load" {
|
||||
want = 1
|
||||
} else if n == "atomic_cas" {
|
||||
want = 3
|
||||
}
|
||||
checkArgs(g, c, 0, want, n)
|
||||
p := expr(g, c.kids[0])
|
||||
if p.ty.kind != "ptr" || p.ty.elem.len == 0 || !isInt(p.ty.elem[0]) {
|
||||
fail(g, c.kids[0], n + "(...) works on a whole number through its address, like " + n + "(addr(count), ...) -- this is " + ast.show(p.ty))
|
||||
}
|
||||
t := p.ty.elem[0]
|
||||
lt := llty(g, t)
|
||||
[]str vs
|
||||
for i in 1..c.kids.len {
|
||||
vs.push(coerce(g, exprWant(g, c.kids[i], t), t, c.kids[i], "the value").v)
|
||||
}
|
||||
if n == "atomic_load" {
|
||||
return Val(v: op(g, "load atomic " + lt + ", ptr " + p.v + " seq_cst, align " + atomicAlign(g, t)), ty: t)
|
||||
}
|
||||
if n == "atomic_store" {
|
||||
emit(g, "store atomic " + lt + " " + vs[0] + ", ptr " + p.v + " seq_cst, align " + atomicAlign(g, t))
|
||||
return Val(ty: T("void"))
|
||||
}
|
||||
if n == "atomic_cas" {
|
||||
r := op(g, "cmpxchg ptr " + p.v + ", " + lt + " " + vs[0] + ", " + lt + " " + vs[1] + " seq_cst seq_cst")
|
||||
return Val(v: op(g, "extractvalue { " + lt + ", i1 } " + r + ", 1"), ty: T("bool"))
|
||||
}
|
||||
how := "add"
|
||||
if n == "atomic_sub" {
|
||||
how = "sub"
|
||||
} else if n == "atomic_swap" {
|
||||
how = "xchg"
|
||||
}
|
||||
return Val(v: op(g, "atomicrmw " + how + " ptr " + p.v + ", " + lt + " " + vs[0] + " seq_cst"), ty: t)
|
||||
}
|
||||
|
||||
// the smaller (o "<") or bigger (o ">") of two numbers, in the type both fit
|
||||
fn pick(ptr[Gen] g, str o, Val a, Val b, ast.Node at) -> Val {
|
||||
l := a
|
||||
|
|
@ -5537,7 +5617,7 @@ fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, st
|
|||
mark := g.pending.len
|
||||
for k in 0..sig.params.len {
|
||||
a := c.kids[first + k]
|
||||
v := coerce(g, exprWant(g, a, sig.params[k]), sig.params[k], a, "argument '" + sig.pnames[k] + "'")
|
||||
v := coerce(g, argWant(g, a, sig.params[k]), sig.params[k], a, "argument '" + sig.pnames[k] + "'")
|
||||
// the argument lives inside the receiver (c.merge(c)): the method gets its own copy
|
||||
if owning(g, sig.params[k]) && !v.fresh && selfRoot.len > 0 && rootVar(a) == selfRoot {
|
||||
v = Val(v: copyValue(g, v.v, v.ty), ty: v.ty, fresh: true)
|
||||
|
|
|
|||
15
src/host.fch
15
src/host.fch
|
|
@ -81,3 +81,18 @@ fn removeTree(str path) {
|
|||
fn move(str from, str to) {
|
||||
shell("mv " + quote(from) + " " + quote(to))
|
||||
}
|
||||
|
||||
// the files under dir (and its folders) whose names end with ext, like ".fch"
|
||||
fn listFiles(str dir, str ext) -> []str {
|
||||
tmp := tempDir() + "/finch_files_" + str(pid()) + ".txt"
|
||||
run("find " + quote(dir) + " -type f -name " + quote("*" + ext) + " > " + quote(tmp) + " 2> " + nullDevice())
|
||||
text := read_file(tmp) or ""
|
||||
remove(tmp)
|
||||
[]str out
|
||||
for line in text.replace("\r", "").split("\n") {
|
||||
if line.ends_with(ext) {
|
||||
out.push(line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,3 +102,18 @@ fn move(str from, str to) {
|
|||
delete_file(to)
|
||||
rename(from, to)
|
||||
}
|
||||
|
||||
// the files under dir (and its folders) whose names end with ext, like ".fch"
|
||||
fn listFiles(str dir, str ext) -> []str {
|
||||
tmp := tempDir() + "/finch_files_" + str(pid()) + ".txt"
|
||||
run("dir /s /b " + quote(dir.replace("/", "\\") + "\\*" + ext) + " > " + quote(tmp) + " 2> " + nullDevice())
|
||||
text := read_file(tmp) or ""
|
||||
remove(tmp)
|
||||
[]str out
|
||||
for line in text.replace("\r", "").split("\n") {
|
||||
if line.ends_with(ext) {
|
||||
out.push(line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,33 @@ fn quote(str a) -> str {
|
|||
return host.quote(a)
|
||||
}
|
||||
|
||||
// a file of the program's own (next to the .fch file), not a library's name
|
||||
fn isOwnCode(str l) -> bool {
|
||||
return l.ends_with(".c") || l.ends_with(".o") || l.ends_with(".a") || l.ends_with(".lib") || isCpp(l)
|
||||
}
|
||||
|
||||
fn isCpp(str l) -> bool {
|
||||
return l.ends_with(".cpp") || l.ends_with(".cc") || l.ends_with(".cxx")
|
||||
}
|
||||
|
||||
// the C++ compiler that goes with a C compiler: clang -> clang++, x86_64-w64-mingw32-gcc -> ...-g++, cc -> c++
|
||||
fn cxxFor(str cc) -> str {
|
||||
if getenv("FINCH_CXX").len > 0 {
|
||||
return getenv("FINCH_CXX")
|
||||
}
|
||||
words := cc.split(" ")
|
||||
first := words[0]
|
||||
if first.ends_with("clang") {
|
||||
first = first + "++"
|
||||
} else if first.ends_with("gcc") {
|
||||
first = first.sub(0, first.len - 3) + "g++"
|
||||
} else if first.ends_with("cc") {
|
||||
first = first.sub(0, first.len - 2) + "c++"
|
||||
}
|
||||
words[0] = first
|
||||
return words.join(" ")
|
||||
}
|
||||
|
||||
// The files a C file was built from, as the compiler's -MMD wrote them: "x.o: x.c x.h \\n y.h"
|
||||
fn depFiles(str dep) -> []str {
|
||||
colon := dep.find(": ")
|
||||
|
|
@ -333,10 +360,22 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
|
|||
host.remove(log)
|
||||
return 1
|
||||
}
|
||||
cmd := cc + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
|
||||
// with C++ files the C++ compiler links, so its library comes along
|
||||
linkWith := cc
|
||||
for l in b.links {
|
||||
if isCpp(l) {
|
||||
linkWith = cxxFor(cc)
|
||||
}
|
||||
}
|
||||
cmd := linkWith + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
|
||||
if !tg.msvc {
|
||||
cmd = cmd + " -lm"
|
||||
}
|
||||
if tg.android {
|
||||
cmd = cmd + " -llog" // bionic has threads built in
|
||||
} else if !tg.windows && !tg.darwin {
|
||||
cmd = cmd + " -lpthread" // std/thread (in glibc since 2.34, needed before)
|
||||
}
|
||||
if b.ldflags.len > 0 {
|
||||
cmd = cmd + b.ldflags // finch.toml: libdirs and ldflags
|
||||
}
|
||||
|
|
@ -351,19 +390,23 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
|
|||
}
|
||||
}
|
||||
for l in b.links {
|
||||
if l.ends_with(".c") {
|
||||
// the program's own C file, built along with it (and kept in the cache until it changes)
|
||||
if l.ends_with(".c") || isCpp(l) {
|
||||
// the program's own C or C++ file, built along with it (and kept in the cache until it changes)
|
||||
pic := " -fPIC"
|
||||
if tg.windows {
|
||||
pic = ""
|
||||
}
|
||||
o := cObject(l, cc, " -O2" + pic + b.cflags, tg, log) or {
|
||||
compiler := cc
|
||||
if isCpp(l) {
|
||||
compiler = cxxFor(cc)
|
||||
}
|
||||
o := cObject(l, compiler, " -O2" + pic + b.cflags, tg, log) or {
|
||||
print(err)
|
||||
host.remove(log)
|
||||
return 1
|
||||
}
|
||||
cmd = cmd + " " + quote(o)
|
||||
} else if l.ends_with(".o") || l.ends_with(".a") {
|
||||
} else if l.ends_with(".o") || l.ends_with(".a") || l.ends_with(".lib") {
|
||||
cmd = cmd + " " + quote(l)
|
||||
} else {
|
||||
cmd = cmd + " " + libFlags(l, tg)
|
||||
|
|
|
|||
28
src/lsp.fch
28
src/lsp.fch
|
|
@ -7,6 +7,7 @@
|
|||
import json
|
||||
import host
|
||||
import docs
|
||||
import fmt
|
||||
import "stdio.h"
|
||||
import "stdlib.h"
|
||||
|
||||
|
|
@ -785,6 +786,7 @@ fn Server.answer(json.Json msg) {
|
|||
caps.set("hoverProvider", json.new_bool(true))
|
||||
caps.set("definitionProvider", json.new_bool(true))
|
||||
caps.set("documentSymbolProvider", json.new_bool(true))
|
||||
caps.set("documentFormattingProvider", json.new_bool(true))
|
||||
comp := json.new_object()
|
||||
dot := json.new_list()
|
||||
dot.push(json.new_text("."))
|
||||
|
|
@ -839,6 +841,32 @@ fn Server.answer(json.Json msg) {
|
|||
empty.set("diagnostics", json.new_list())
|
||||
notify("textDocument/publishDiagnostics", empty)
|
||||
}
|
||||
"textDocument/formatting" -> {
|
||||
// finch fmt on the open text: one edit that puts the whole text in its place
|
||||
path := pathOf(params)
|
||||
edits := json.new_list()
|
||||
if self.docs.has(path) {
|
||||
old := self.docs[path]
|
||||
neat := fmt.format(old)
|
||||
if neat != old {
|
||||
lines := old.split("\n")
|
||||
whole := json.new_object()
|
||||
start := json.new_object()
|
||||
start.set("line", num(0))
|
||||
start.set("character", num(0))
|
||||
end := json.new_object()
|
||||
end.set("line", num(lines.len))
|
||||
end.set("character", num(0))
|
||||
whole.set("start", start)
|
||||
whole.set("end", end)
|
||||
edit := json.new_object()
|
||||
edit.set("range", whole)
|
||||
edit.set("newText", json.new_text(neat))
|
||||
edits.push(edit)
|
||||
}
|
||||
}
|
||||
reply(id, edits)
|
||||
}
|
||||
"textDocument/hover", "textDocument/definition", "textDocument/completion", "textDocument/documentSymbol", "textDocument/signatureHelp" -> {
|
||||
path := pathOf(params)
|
||||
line := params.get("position").get("line").int_or(0) + 1
|
||||
|
|
|
|||
131
src/main.fch
131
src/main.fch
|
|
@ -15,6 +15,8 @@ import linker
|
|||
import lsp
|
||||
import host
|
||||
import embedded
|
||||
import manifest
|
||||
import fmt
|
||||
import "stdlib.h"
|
||||
|
||||
fn dirOf(str path) -> str {
|
||||
|
|
@ -160,6 +162,8 @@ fn usage() {
|
|||
print(" finch build <file.fch> [-o name] build a program")
|
||||
print(" finch test <file.fch> run its test \"...\" { } blocks (and those of the modules it imports)")
|
||||
print(" finch ir <file.fch> print the LLVM IR")
|
||||
print(" finch fmt [--check] [files/dirs] indent .fch files by { }, 4 spaces (--check: only list those it would change)")
|
||||
print(" (with a finch.toml, run, build and test can leave out the file: its entry is used)")
|
||||
print(" finch version show the version")
|
||||
print(" finch lsp the language server, for editors (VS Code, Kate, ...)")
|
||||
print("options:")
|
||||
|
|
@ -190,23 +194,98 @@ fn unpack() -> str {
|
|||
return dir
|
||||
}
|
||||
|
||||
// finch fmt [--check] [files or folders]: the indentation and empty lines of .fch files (all under here by default)
|
||||
fn format([]str args) -> int {
|
||||
check := false
|
||||
[]str targets
|
||||
for i in 2..args.len {
|
||||
if args[i] == "--check" {
|
||||
check = true
|
||||
} else {
|
||||
targets.push(args[i])
|
||||
}
|
||||
}
|
||||
if targets.len == 0 {
|
||||
targets.push(".")
|
||||
}
|
||||
[]str files
|
||||
for t in targets {
|
||||
if t.ends_with(".fch") {
|
||||
files.push(t)
|
||||
} else {
|
||||
for f in host.listFiles(t, ".fch") {
|
||||
files.push(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
changed := 0
|
||||
for f in files {
|
||||
text := read_file(f) or {
|
||||
print("error: can't read " + f)
|
||||
return 1
|
||||
}
|
||||
neat := fmt.format(text)
|
||||
if neat != text {
|
||||
changed += 1
|
||||
if check {
|
||||
print(f)
|
||||
} else {
|
||||
write_file(f, neat)
|
||||
}
|
||||
}
|
||||
}
|
||||
if check && changed > 0 {
|
||||
print(str(changed) + " file(s) are not formatted (finch fmt changes them)")
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main([]str args) -> int {
|
||||
if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
|
||||
print("Finch 2.4.0 (the compiler is written in Finch)")
|
||||
return 0
|
||||
}
|
||||
if args.len >= 2 && args[1] == "fmt" {
|
||||
return format(args)
|
||||
}
|
||||
if args.len == 2 && args[1] == "lsp" {
|
||||
return lsp.run(args[0]) // the language server, for editors
|
||||
}
|
||||
if args.len < 3 {
|
||||
if args.len < 2 {
|
||||
usage()
|
||||
}
|
||||
cmd := args[1]
|
||||
file := args[2]
|
||||
if cmd != "build" && cmd != "run" && cmd != "ir" && cmd != "index" && cmd != "test" {
|
||||
usage()
|
||||
}
|
||||
file := ""
|
||||
ai := 2
|
||||
if args.len > 2 && !args[2].starts_with("-") {
|
||||
file = args[2]
|
||||
ai = 3
|
||||
}
|
||||
// finch.toml: next to the file, or above it (with no file: from here, and its entry is the file)
|
||||
from := host.cwd()
|
||||
if file.len > 0 {
|
||||
from = dirOf(absolute(file))
|
||||
}
|
||||
man := manifest.find(from) or {
|
||||
print(err)
|
||||
return 1
|
||||
}
|
||||
if file.len == 0 {
|
||||
entry := man.value("entry", "")
|
||||
if !man.found || entry.len == 0 {
|
||||
print("error: which file? (finch " + cmd + " <file.fch>, or a finch.toml with entry = \"main.fch\")")
|
||||
return 1
|
||||
}
|
||||
file = man.resolve(entry)
|
||||
}
|
||||
out := stem(file)
|
||||
if man.found && man.value("name", "").len > 0 {
|
||||
out = man.value("name", "")
|
||||
}
|
||||
outGiven := false
|
||||
shared := false
|
||||
debug := false
|
||||
|
|
@ -219,7 +298,6 @@ fn main([]str args) -> int {
|
|||
[]str overlayPaths
|
||||
[]str overlayFiles
|
||||
[]str progArgs
|
||||
ai := 3
|
||||
while ai < args.len {
|
||||
a := args[ai]
|
||||
if cmd == "run" && progArgs.len > 0 { // after the first argument of the program, all of them are its
|
||||
|
|
@ -298,6 +376,48 @@ fn main([]str args) -> int {
|
|||
}
|
||||
loader.overlayPaths = overlayPaths
|
||||
loader.overlayFiles = overlayFiles
|
||||
// what finch.toml adds, for every system and for this one
|
||||
[]str targets
|
||||
sys := "linux"
|
||||
if tg.windows {
|
||||
sys = "windows"
|
||||
} else if tg.darwin {
|
||||
sys = "macos"
|
||||
} else if tg.android {
|
||||
sys = "android"
|
||||
}
|
||||
arch := "x86_64"
|
||||
if tg.arm64 {
|
||||
arch = "arm64"
|
||||
}
|
||||
targets.push(sys)
|
||||
targets.push(sys + "-" + arch)
|
||||
ldflags := ""
|
||||
cflags := ""
|
||||
if man.found {
|
||||
for d in man.list("paths", targets) {
|
||||
loader.paths.push(man.resolve(d))
|
||||
}
|
||||
for d in man.list("include", targets) {
|
||||
extraDirs.push(man.resolve(d))
|
||||
}
|
||||
for l in man.list("links", targets) {
|
||||
if l.ends_with(".c") || l.ends_with(".o") || l.ends_with(".a") || l.ends_with(".cpp") || l.ends_with(".cc") {
|
||||
extraLibs.push(man.resolve(l))
|
||||
} else {
|
||||
extraLibs.push(l)
|
||||
}
|
||||
}
|
||||
for d in man.list("libdirs", targets) {
|
||||
ldflags = ldflags + " -L" + linker.quote(man.resolve(d))
|
||||
}
|
||||
for f in man.list("ldflags", targets) {
|
||||
ldflags = ldflags + " " + linker.quote(f)
|
||||
}
|
||||
for f in man.list("cflags", targets) {
|
||||
cflags = cflags + " " + linker.quote(f)
|
||||
}
|
||||
}
|
||||
for d in getenv("FINCH_PATH").split(host.pathSeparator()) {
|
||||
if d.len > 0 {
|
||||
loader.paths.push(d)
|
||||
|
|
@ -322,7 +442,7 @@ fn main([]str args) -> int {
|
|||
}
|
||||
}
|
||||
for l in prog.links {
|
||||
if l.ends_with(".c") || l.ends_with(".o") || l.ends_with(".a") {
|
||||
if linker.isOwnCode(l) {
|
||||
links.push(d + "/" + l) // the program's own C code, next to the .fch file
|
||||
} else {
|
||||
links.push(l)
|
||||
|
|
@ -377,7 +497,8 @@ fn main([]str args) -> int {
|
|||
for d in extraDirs {
|
||||
cflagList.push(" -I" + linker.quote(d))
|
||||
}
|
||||
if linker.build(linker.Build(out: out, ir: ir, rt: rt, opt: opt, extra: extra, shared: shared, links: links, name: absolute(file), cflags: cflagList.join("")), tg, ci) != 0 {
|
||||
cflagList.push(cflags)
|
||||
if linker.build(linker.Build(out: out, ir: ir, rt: rt, opt: opt, extra: extra, shared: shared, links: links, name: absolute(file), cflags: cflagList.join(""), ldflags: ldflags), tg, ci) != 0 {
|
||||
return 1
|
||||
}
|
||||
if cmd == "run" || cmd == "test" {
|
||||
|
|
|
|||
243
src/manifest.fch
Normal file
243
src/manifest.fch
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
// finch.toml: what a project needs to build, in one file next to its code (instead of FINCH_PATH,
|
||||
// C_INCLUDE_PATH, -I, -l and linker flags on the command line).
|
||||
//
|
||||
// name = "game" the program finch build makes
|
||||
// entry = "src/main.fch" what finch run / build / test use when no file is given
|
||||
// paths = ["src", "lib"] folders with modules
|
||||
// include = ["vendor/SDL3/include"] folders with C headers (also for link "x.c" files)
|
||||
// links = ["SDL3"] libraries
|
||||
// libdirs = ["vendor/SDL3/lib"] folders with libraries
|
||||
// cflags = ["-DFAST"] for the program's own C files
|
||||
// ldflags = ["-Wl,-rpath,."] for the linker
|
||||
//
|
||||
// [target.windows] added only when building for that system:
|
||||
// links = ["ws2_32"] windows, linux, macos, android, or with the CPU: linux-arm64
|
||||
//
|
||||
// Folders are relative to finch.toml. Only this part of TOML is read: [sections], key = "text",
|
||||
// key = ["a", "b"] (over several lines too), numbers, true/false and # comments.
|
||||
|
||||
struct Manifest {
|
||||
bool found
|
||||
str path
|
||||
str dir
|
||||
map[str]str values // "section.key" (top level: "key") -> a single value
|
||||
map[str][]str lists // "section.key" -> a list
|
||||
}
|
||||
|
||||
fn dirOf(str path) -> str {
|
||||
i := path.len - 1
|
||||
while i >= 0 && path[i] != '/' && path[i] != '\\' {
|
||||
i -= 1
|
||||
}
|
||||
if i < 0 {
|
||||
return "."
|
||||
}
|
||||
if i == 0 {
|
||||
return "/"
|
||||
}
|
||||
return path.sub(0, i)
|
||||
}
|
||||
|
||||
// finch.toml in `dir` or the folders above it
|
||||
fn find(str dir) -> Manifest! {
|
||||
d := dir
|
||||
while true {
|
||||
p := d + "/finch.toml"
|
||||
if d == "/" {
|
||||
p = "/finch.toml"
|
||||
}
|
||||
if file_exists(p) {
|
||||
return try parse(p, try read_file(p))
|
||||
}
|
||||
up := dirOf(d)
|
||||
if up == d || d == "." || d.len == 0 {
|
||||
break
|
||||
}
|
||||
d = up
|
||||
}
|
||||
return Manifest()
|
||||
}
|
||||
|
||||
struct Reader {
|
||||
str text
|
||||
int i
|
||||
int line
|
||||
str path
|
||||
}
|
||||
|
||||
fn Reader.fail(str msg) -> str {
|
||||
return self.path + ":" + str(self.line) + ": error: " + msg
|
||||
}
|
||||
|
||||
fn Reader.at() -> char {
|
||||
if self.i < self.text.len {
|
||||
return self.text[self.i]
|
||||
}
|
||||
return char(0)
|
||||
}
|
||||
|
||||
// past spaces, and past line ends and comments too when `lines`
|
||||
fn Reader.skip(bool lines) {
|
||||
while self.i < self.text.len {
|
||||
c := self.text[self.i]
|
||||
if c == ' ' || c == '\t' || c == '\r' {
|
||||
self.i += 1
|
||||
} else if c == '#' {
|
||||
while self.i < self.text.len && self.text[self.i] != '\n' {
|
||||
self.i += 1
|
||||
}
|
||||
} else if c == '\n' && lines {
|
||||
self.i += 1
|
||||
self.line += 1
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isKeyChar(char c) -> bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.'
|
||||
}
|
||||
|
||||
fn Reader.word() -> str {
|
||||
start := self.i
|
||||
while self.i < self.text.len && isKeyChar(self.text[self.i]) {
|
||||
self.i += 1
|
||||
}
|
||||
return self.text.sub(start, self.i)
|
||||
}
|
||||
|
||||
// "text" (with \" \\ \n \t) or 'text' (as it is)
|
||||
fn Reader.quoted() -> str! {
|
||||
q := self.at()
|
||||
self.i += 1
|
||||
[]str out
|
||||
while self.at() != q {
|
||||
c := self.at()
|
||||
if c == char(0) || c == '\n' {
|
||||
return error(self.fail("this text is never closed"))
|
||||
}
|
||||
if c == '\\' && q == '"' {
|
||||
self.i += 1
|
||||
e := self.at()
|
||||
if e == 'n' {
|
||||
c = '\n'
|
||||
} else if e == 't' {
|
||||
c = '\t'
|
||||
} else {
|
||||
c = e
|
||||
}
|
||||
}
|
||||
out.push(str(c))
|
||||
self.i += 1
|
||||
}
|
||||
self.i += 1
|
||||
return out.join("")
|
||||
}
|
||||
|
||||
// a single value: text, a number, true or false (all kept as text)
|
||||
fn Reader.scalar() -> str! {
|
||||
c := self.at()
|
||||
if c == '"' || c == '\'' {
|
||||
return try self.quoted()
|
||||
}
|
||||
w := self.word()
|
||||
if w.len == 0 {
|
||||
return error(self.fail("expected a value: \"text\", a number, true, false or a [list]"))
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
fn parse(str path, str text) -> Manifest! {
|
||||
m := Manifest(found: true, path: path, dir: dirOf(path))
|
||||
r := Reader(text: text, i: 0, line: 1, path: path)
|
||||
section := ""
|
||||
while true {
|
||||
r.skip(true)
|
||||
if r.i >= r.text.len {
|
||||
break
|
||||
}
|
||||
if r.at() == '[' {
|
||||
r.i += 1
|
||||
r.skip(false)
|
||||
section = r.word()
|
||||
r.skip(false)
|
||||
if r.at() != ']' || section.len == 0 {
|
||||
return error(r.fail("a section looks like [name] or [target.windows]"))
|
||||
}
|
||||
r.i += 1
|
||||
continue
|
||||
}
|
||||
key := r.word()
|
||||
if key.len == 0 {
|
||||
return error(r.fail("expected a key, like name = \"game\""))
|
||||
}
|
||||
r.skip(false)
|
||||
if r.at() != '=' {
|
||||
return error(r.fail("expected '=' after '" + key + "'"))
|
||||
}
|
||||
r.i += 1
|
||||
r.skip(false)
|
||||
full := key
|
||||
if section.len > 0 {
|
||||
full = section + "." + key
|
||||
}
|
||||
if r.at() == '[' {
|
||||
r.i += 1
|
||||
[]str items
|
||||
while true {
|
||||
r.skip(true)
|
||||
if r.at() == ']' {
|
||||
r.i += 1
|
||||
break
|
||||
}
|
||||
items.push(try r.scalar())
|
||||
r.skip(true)
|
||||
if r.at() == ',' {
|
||||
r.i += 1
|
||||
} else if r.at() != ']' {
|
||||
return error(r.fail("expected ',' or ']' in the list of '" + key + "'"))
|
||||
}
|
||||
}
|
||||
m.lists[full] = items
|
||||
} else {
|
||||
m.values[full] = try r.scalar()
|
||||
}
|
||||
r.skip(false)
|
||||
if r.i < r.text.len && r.at() != '\n' {
|
||||
return error(r.fail("one key per line"))
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
fn Manifest.value(str key, str fallback) -> str {
|
||||
return self.values.get(key, fallback)
|
||||
}
|
||||
|
||||
// the list `key` from the top level and from the sections for this system, in that order
|
||||
fn Manifest.list(str key, []str targets) -> []str {
|
||||
[]str out
|
||||
if self.lists.has(key) {
|
||||
for x in self.lists[key] {
|
||||
out.push(x)
|
||||
}
|
||||
}
|
||||
for t in targets {
|
||||
k := "target." + t + "." + key
|
||||
if self.lists.has(k) {
|
||||
for x in self.lists[k] {
|
||||
out.push(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// a folder or file of the manifest, from where finch.toml is
|
||||
fn Manifest.resolve(str p) -> str {
|
||||
if p.starts_with("/") || (p.len > 2 && p[1] == ':') {
|
||||
return p
|
||||
}
|
||||
return self.dir + "/" + p
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
|||
} else if at(p, "link") {
|
||||
lt := next(p)
|
||||
lib := expect(p, "string", "a library name in quotes, like link \"glfw\"").text
|
||||
ownCode := lib.ends_with(".c") || lib.ends_with(".o") || lib.ends_with(".a") // your own C code, next to the .fch file
|
||||
ownCode := lib.ends_with(".c") || lib.ends_with(".o") || lib.ends_with(".a") || lib.ends_with(".cpp") || lib.ends_with(".cc") || lib.ends_with(".cxx") || lib.ends_with(".lib") // your own C or C++ code, next to the .fch file
|
||||
if !ownCode && (lib.starts_with("lib") || lib.contains(".so") || lib.contains("/")) {
|
||||
fail(p, lt, "write just the library's name: for libglfw.so that's link \"glfw\"")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ struct Target {
|
|||
bool darwin // macOS
|
||||
bool arm64 // AAPCS64
|
||||
bool cross // another system than the one the compiler runs on
|
||||
bool android // Android (bionic): a .so that an app loads
|
||||
str exe // ".exe" or ""
|
||||
str cc // the C compiler that links (the IR is always compiled by clang)
|
||||
str runner // how to run a program for it here: "", "wine ", "qemu-aarch64 "
|
||||
|
|
@ -45,6 +46,8 @@ fn select(str name) -> Target {
|
|||
t = "x86_64-unknown-linux-gnu"
|
||||
} else if name == "arm64" || name == "aarch64" || name == "linux-arm64" {
|
||||
t = "aarch64-unknown-linux-gnu"
|
||||
} else if name == "android" {
|
||||
t = "aarch64-linux-android24"
|
||||
} else if name == "macos" {
|
||||
if hostName.starts_with("aarch64") || hostName.starts_with("arm64") {
|
||||
t = "arm64-apple-macosx14.0.0"
|
||||
|
|
@ -60,6 +63,7 @@ fn select(str name) -> Target {
|
|||
tg.msvc = t.contains("msvc")
|
||||
tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos")
|
||||
tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64")
|
||||
tg.android = t.contains("android")
|
||||
hostArm := hostName.starts_with("aarch64") || hostName.starts_with("arm64")
|
||||
hostDarwin := hostName.contains("apple") || hostName.contains("darwin")
|
||||
hostWin := hostName.contains("windows") || hostName.contains("mingw") || hostName.contains("msvc")
|
||||
|
|
@ -72,6 +76,8 @@ fn select(str name) -> Target {
|
|||
fc := getenv("FINCH_CC")
|
||||
if fc.len > 0 {
|
||||
tg.cc = fc
|
||||
} else if tg.android {
|
||||
tg.cc = androidClang(t)
|
||||
} else if tg.cross && tg.windows {
|
||||
tg.cc = "x86_64-w64-mingw32-gcc"
|
||||
} else if tg.cross && tg.arm64 && !tg.darwin {
|
||||
|
|
@ -85,12 +91,30 @@ fn select(str name) -> Target {
|
|||
}
|
||||
if tg.cross && tg.windows {
|
||||
tg.runner = "wine "
|
||||
} else if tg.cross && tg.arm64 && !tg.darwin {
|
||||
} else if tg.cross && tg.arm64 && !tg.darwin && !tg.android {
|
||||
tg.runner = "qemu-aarch64 "
|
||||
}
|
||||
return tg
|
||||
}
|
||||
|
||||
// The NDK's clang for this Android triple (ANDROID_NDK_HOME or ANDROID_NDK_ROOT says where the NDK is)
|
||||
fn androidClang(str triple) -> str {
|
||||
ndk := getenv("ANDROID_NDK_HOME")
|
||||
if ndk.len == 0 {
|
||||
ndk = getenv("ANDROID_NDK_ROOT")
|
||||
}
|
||||
if ndk.len == 0 {
|
||||
return triple + "-clang"
|
||||
}
|
||||
for h in ["linux-x86_64", "darwin-x86_64", "windows-x86_64"] {
|
||||
cc := ndk + "/toolchains/llvm/prebuilt/" + h + "/bin/" + triple + "-clang"
|
||||
if file_exists(cc) || file_exists(cc + ".cmd") {
|
||||
return cc
|
||||
}
|
||||
}
|
||||
return triple + "-clang"
|
||||
}
|
||||
|
||||
// C's plain `char`: signed on x86 and Apple, not on ARM64 Linux
|
||||
fn charIsSigned(Target t) -> bool {
|
||||
return !t.arm64 || t.darwin || t.windows
|
||||
|
|
|
|||
8
std/finch_thread.h
Normal file
8
std/finch_thread.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/* The runtime's thread functions, for std/thread.fch (they are in runtime/finch_rt.c, linked into every program). */
|
||||
void *finch_thread_spawn(void (*fn)(void *), void *arg);
|
||||
int finch_thread_join(void *thread);
|
||||
void *finch_mutex_new(void);
|
||||
void finch_mutex_lock(void *mutex);
|
||||
void finch_mutex_unlock(void *mutex);
|
||||
void finch_mutex_free(void *mutex);
|
||||
int finch_cpu_count(void);
|
||||
65
std/thread.fch
Normal file
65
std/thread.fch
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Threads: run a function next to the rest of the program, and a lock for what they share.
|
||||
//
|
||||
// import thread
|
||||
// fn work(ptr arg) { a thread's function takes one ptr (to whatever it needs)
|
||||
// ptr[Job] job = arg
|
||||
// ...
|
||||
// }
|
||||
// t := try thread.spawn(addr(work), addr(job))
|
||||
// t.join() wait for it; `job` must live until then
|
||||
//
|
||||
// m := thread.mutex()
|
||||
// defer m.close()
|
||||
// m.lock() ... m.unlock() one thread at a time in between
|
||||
// atomic_add(addr(count), 1) (built in) numbers changed by many threads at once
|
||||
//
|
||||
// What a thread is given is shared, not copied: don't let it outlive the block it comes from, and
|
||||
// change it only under a lock (or with atomic_...).
|
||||
import "finch_thread.h"
|
||||
|
||||
struct Thread {
|
||||
ptr handle
|
||||
}
|
||||
|
||||
struct Mutex {
|
||||
ptr handle
|
||||
}
|
||||
|
||||
// start fn(arg) on a new thread; fn is addr(f) of a fn f(ptr arg)
|
||||
fn spawn(ptr fn_, ptr arg) -> Thread! {
|
||||
h := finch_thread_spawn(fn_, arg)
|
||||
if h == null {
|
||||
return error("can't start a thread")
|
||||
}
|
||||
return Thread(handle: h)
|
||||
}
|
||||
|
||||
// wait until the thread has finished
|
||||
fn Thread.join() {
|
||||
if self.handle != null {
|
||||
finch_thread_join(self.handle)
|
||||
self.handle = null
|
||||
}
|
||||
}
|
||||
|
||||
fn mutex() -> Mutex {
|
||||
return Mutex(handle: finch_mutex_new())
|
||||
}
|
||||
|
||||
fn Mutex.lock() {
|
||||
finch_mutex_lock(self.handle)
|
||||
}
|
||||
|
||||
fn Mutex.unlock() {
|
||||
finch_mutex_unlock(self.handle)
|
||||
}
|
||||
|
||||
fn Mutex.close() {
|
||||
finch_mutex_free(self.handle)
|
||||
self.handle = null
|
||||
}
|
||||
|
||||
// how many threads the computer runs at the same time
|
||||
fn cpus() -> int {
|
||||
return int(finch_cpu_count())
|
||||
}
|
||||
|
|
@ -124,6 +124,12 @@ syms = c.request("textDocument/documentSymbol", {"textDocument": td})
|
|||
names = sorted(s["name"] for s in syms)
|
||||
check("document symbols", names == ["Point", "add", "main"], names)
|
||||
|
||||
messy = "fn main() {\n x := 1 \n\n\n\tprint(x)\n}"
|
||||
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 5}, "contentChanges": [{"text": messy}]})
|
||||
fm = c.request("textDocument/formatting", {"textDocument": td, "options": {"tabSize": 4, "insertSpaces": True}})
|
||||
check("formatting", fm and fm[0]["newText"] == "fn main() {\n x := 1\n\n print(x)\n}\n", fm)
|
||||
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 5}, "contentChanges": [{"text": src}]})
|
||||
|
||||
bad = src.replace("total := add(p.x, p.y)", 'total := add(p.x, "y")')
|
||||
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 6}, "contentChanges": [{"text": bad}]})
|
||||
diags = c.diagnostics(uri)
|
||||
|
|
|
|||
23
tests/new/auto_addr.fch
Normal file
23
tests/new/auto_addr.fch
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
struct Body {
|
||||
float x
|
||||
float y
|
||||
}
|
||||
|
||||
fn push(ptr[Body] b, float dx) {
|
||||
b.x += dx
|
||||
}
|
||||
|
||||
fn Body.moved(float dx) -> Body {
|
||||
return Body(x: self.x + dx, y: self.y)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
b := Body(x: 1.0, y: 2.0)
|
||||
push(b, 0.5) // the same as push(addr(b), 0.5)
|
||||
push(addr(b), 0.5)
|
||||
print(b.x)
|
||||
p := addr(b)
|
||||
push(p, 1.0) // already a ptr: as it is
|
||||
print(b.x)
|
||||
print(b.moved(1.0).x)
|
||||
}
|
||||
3
tests/new/auto_addr.out
Normal file
3
tests/new/auto_addr.out
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
2
|
||||
3
|
||||
4
|
||||
|
|
@ -24,4 +24,6 @@ fn main() {
|
|||
t.as.f = 0.5
|
||||
print(t.as.f)
|
||||
print(abs(-2.5), abs(-3), min(EV_KEY, 7)) // Finch's abs, not C's int-only one
|
||||
bg := RAYWHITE // #define RAYWHITE (Color){ 245, 245, 245, 255 }
|
||||
print(bg.r, bg.a, DARK.g, DARK.a, HALF_UP.x, HALF_UP.y)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ true 32
|
|||
32 2
|
||||
0.5
|
||||
2.5 3 7
|
||||
245 255 20 0 0.5 -1
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ typedef struct { int kind; union { int i; float f; } as; } Tagged;
|
|||
#define LONG_NAME "a" "b"
|
||||
#define NOT_A_CONSTANT do { } while (0)
|
||||
|
||||
typedef struct { unsigned char r, g, b, a; } Color;
|
||||
typedef struct { float x, y; } Vec2;
|
||||
#define CLITERAL(type) (type)
|
||||
#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 }
|
||||
#define DARK (Color){ 20, 20, 20 }
|
||||
#define HALF_UP (Vec2){ 0.5f, -1 }
|
||||
#define NOT_A_STRUCT_CONST (Color){ 1, 2, (unsigned char)(long)&poll_event, 4 }
|
||||
|
||||
static inline int fill_key(Event *e, int key) {
|
||||
e->key.type = EV_KEY;
|
||||
e->key.key = key;
|
||||
|
|
|
|||
13
tests/new/fail/auto_addr_value.fch
Normal file
13
tests/new/fail/auto_addr_value.fch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// expect: argument 'b' must be ptr[Body], but this is Body
|
||||
struct Body {
|
||||
float x
|
||||
}
|
||||
fn push(ptr[Body] b) {
|
||||
b.x += 1.0
|
||||
}
|
||||
fn make() -> Body {
|
||||
return Body(x: 1.0)
|
||||
}
|
||||
fn main() {
|
||||
push(make())
|
||||
}
|
||||
21
tests/new/interpolation.fch
Normal file
21
tests/new/interpolation.fch
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// "\(x)" in text
|
||||
struct P {
|
||||
int x
|
||||
float y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
name := "Ada"
|
||||
hp := 7
|
||||
p := P(x: 3, y: 0.25)
|
||||
print("hi \(name), hp \(hp), next \(hp + 1)")
|
||||
print("p = (\(p.x), \(p.y, 2)) \("nested \(name)")")
|
||||
map[str]int m
|
||||
m["k"] = 5
|
||||
print("\(m["k"]) \(str(1.0 / 3.0, 3))")
|
||||
print("no code here: \\(x) and \t tab")
|
||||
long := """
|
||||
speed \(p.y * 4.0)
|
||||
"""
|
||||
print(long)
|
||||
}
|
||||
5
tests/new/interpolation.out
Normal file
5
tests/new/interpolation.out
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
hi Ada, hp 7, next 8
|
||||
p = (3, 0.25) nested Ada
|
||||
5 0.333
|
||||
no code here: \(x) and tab
|
||||
speed 1
|
||||
55
tests/new/threads.fch
Normal file
55
tests/new/threads.fch
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import thread
|
||||
|
||||
struct Shared {
|
||||
[]int seen
|
||||
}
|
||||
|
||||
struct Job {
|
||||
ptr[int] counter
|
||||
int adds
|
||||
ptr[thread.Mutex] lock
|
||||
ptr[Shared] common
|
||||
int id
|
||||
}
|
||||
|
||||
fn work(ptr arg) {
|
||||
ptr[Job] job = arg
|
||||
for i in 0..job.adds {
|
||||
atomic_add(job.counter, 1)
|
||||
}
|
||||
job.lock.lock()
|
||||
job.common.seen.push(job.id)
|
||||
job.lock.unlock()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
count := 0
|
||||
m := thread.mutex()
|
||||
defer m.close()
|
||||
both := Shared()
|
||||
[]Job jobs
|
||||
for i in 0..4 {
|
||||
jobs.push(Job(counter: addr(count), adds: 100000, lock: addr(m), common: addr(both), id: i))
|
||||
}
|
||||
[]thread.Thread ts
|
||||
for i in 0..4 {
|
||||
ts.push(thread.spawn(addr(work), addr(jobs[i])) or {
|
||||
print("can't start")
|
||||
exit(1)
|
||||
})
|
||||
}
|
||||
for i in 0..4 {
|
||||
ts[i].join()
|
||||
}
|
||||
print(atomic_load(addr(count)))
|
||||
print(both.seen.len)
|
||||
print(thread.cpus() > 0)
|
||||
|
||||
u32 flags = 5
|
||||
print(atomic_swap(addr(flags), 9))
|
||||
print(atomic_cas(addr(flags), 9, 1))
|
||||
print(atomic_cas(addr(flags), 9, 2))
|
||||
atomic_store(addr(flags), 7)
|
||||
print(atomic_sub(addr(flags), 2))
|
||||
print(flags)
|
||||
}
|
||||
8
tests/new/threads.out
Normal file
8
tests/new/threads.out
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
400000
|
||||
4
|
||||
true
|
||||
5
|
||||
true
|
||||
false
|
||||
7
|
||||
5
|
||||
18
tests/run.sh
18
tests/run.sh
|
|
@ -56,6 +56,24 @@ for f in fail/*.fch new/fail/*.fch; do
|
|||
fi
|
||||
done
|
||||
|
||||
# the commands around the compiler: finch test, the chain of calls, finch fmt, a finch.toml project
|
||||
check_tool() { # name, what it printed, the file with what it should print
|
||||
if [ "$2" == "$(tr -d '\r' < "$3")" ]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
echo "FAIL $1"
|
||||
diff <(echo "$2") "$3" | head -20
|
||||
fi
|
||||
}
|
||||
check_tool "finch test" "$(cd tool && "$FINCH" test test_blocks.fch 2>&1 | tr -d '\r')" tool/test_blocks.out
|
||||
check_tool "the chain of calls" "$(cd tool && "$FINCH" run trace.fch 2>&1 | tr -d '\r')" tool/trace.out
|
||||
cp tool/fmt_in.fch "$tmp/fmt.fch"
|
||||
"$FINCH" fmt "$tmp/fmt.fch"
|
||||
check_tool "finch fmt" "$(cat "$tmp/fmt.fch")" tool/fmt_out.fch
|
||||
check_tool "finch fmt (again)" "$("$FINCH" fmt --check tool/fmt_out.fch 2>&1)" /dev/null
|
||||
check_tool "finch.toml" "$(cd tool/project && "$FINCH" run 2>&1 | tr -d '\r')" tool/project/expected.out
|
||||
|
||||
# the same programs with debug info (-g -O0): the metadata has to be valid for every kind of thing a program has
|
||||
for f in new/enums.fch new/generics.fch new/std_json.fch run/basics.fch run/methods_basic.fch run/maps.fch run/errors.fch \
|
||||
run/defer.fch run/c_structs.fch run/memory.fch; do
|
||||
|
|
|
|||
24
tests/tool/fmt_in.fch
Normal file
24
tests/tool/fmt_in.fch
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
struct P {
|
||||
int x
|
||||
int y
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
p := P(x: 1,
|
||||
y: 2)
|
||||
msg := """
|
||||
a {
|
||||
b
|
||||
"""
|
||||
if p.x > 0 {
|
||||
print("{ \(p.x) }") // } not counted
|
||||
} else {
|
||||
print(msg)
|
||||
}
|
||||
s := "a" +
|
||||
"b"
|
||||
print(s)
|
||||
}
|
||||
|
||||
21
tests/tool/fmt_out.fch
Normal file
21
tests/tool/fmt_out.fch
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
struct P {
|
||||
int x
|
||||
int y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
p := P(x: 1,
|
||||
y: 2)
|
||||
msg := """
|
||||
a {
|
||||
b
|
||||
"""
|
||||
if p.x > 0 {
|
||||
print("{ \(p.x) }") // } not counted
|
||||
} else {
|
||||
print(msg)
|
||||
}
|
||||
s := "a" +
|
||||
"b"
|
||||
print(s)
|
||||
}
|
||||
1
tests/tool/project/expected.out
Normal file
1
tests/tool/project/expected.out
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello world 42
|
||||
14
tests/tool/project/finch.toml
Normal file
14
tests/tool/project/finch.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# a project: finch run / build / test here need no file and no flags
|
||||
name = "mygame"
|
||||
entry = "src/main.fch"
|
||||
paths = ["src"]
|
||||
include = ["native"]
|
||||
links = [
|
||||
"native/glue.cpp", # C++, built with the C++ compiler
|
||||
]
|
||||
cflags = ["-DGLUE_BASE=40"]
|
||||
|
||||
[target.linux]
|
||||
links = ["m"]
|
||||
[target.windows]
|
||||
links = ["ws2_32"]
|
||||
9
tests/tool/project/native/glue.cpp
Normal file
9
tests/tool/project/native/glue.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// C++ behind a C header: extern "C" gives the function a name C (and Finch) can call
|
||||
#include "glue.h"
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
extern "C" int glue_answer(void) {
|
||||
std::vector<int> v = {1, 1};
|
||||
return GLUE_BASE + std::accumulate(v.begin(), v.end(), 0);
|
||||
}
|
||||
7
tests/tool/project/native/glue.h
Normal file
7
tests/tool/project/native/glue.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
int glue_answer(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
3
tests/tool/project/src/game/world.fch
Normal file
3
tests/tool/project/src/game/world.fch
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn hello() -> str {
|
||||
return "world"
|
||||
}
|
||||
6
tests/tool/project/src/main.fch
Normal file
6
tests/tool/project/src/main.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import "glue.h"
|
||||
import game/world
|
||||
|
||||
fn main() {
|
||||
print("hello \(world.hello()) \(glue_answer())")
|
||||
}
|
||||
30
tests/tool/test_blocks.fch
Normal file
30
tests/tool/test_blocks.fch
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// finch test: each test "..." block runs on its own; a failed assert or a runtime error fails only that one
|
||||
fn add(int a, int b) -> int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn second([]int xs) -> int {
|
||||
return xs[1]
|
||||
}
|
||||
|
||||
test "add works" {
|
||||
assert(add(2, 3) == 5)
|
||||
assert(add(-1, 1) == 0, "zero")
|
||||
}
|
||||
|
||||
test "a failing assert" {
|
||||
x := add(1, 2)
|
||||
assert(x == 4, "x is \(x), not 4")
|
||||
}
|
||||
|
||||
test "a runtime error fails only this test" {
|
||||
print(second([1]))
|
||||
}
|
||||
|
||||
test "still runs after the others" {
|
||||
assert("\(1.5, 2) and \(add(1, 1))" == "1.50 and 2")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print("main is not run by finch test")
|
||||
}
|
||||
8
tests/tool/test_blocks.out
Normal file
8
tests/tool/test_blocks.out
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
ok add works
|
||||
FAIL a failing assert
|
||||
test_blocks.fch:17: runtime error: assert failed: x is 3, not 4
|
||||
FAIL a runtime error fails only this test
|
||||
test_blocks.fch:7: runtime error: index 1 is out of range (the length is 1)
|
||||
ok still runs after the others
|
||||
|
||||
2 passed, 2 failed
|
||||
16
tests/tool/trace.fch
Normal file
16
tests/tool/trace.fch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
struct Enemy {
|
||||
[]int path
|
||||
}
|
||||
|
||||
fn Enemy.next(int i) -> int {
|
||||
return self.path[i]
|
||||
}
|
||||
|
||||
fn step(Enemy e) -> int {
|
||||
return e.next(3)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
e := Enemy(path: [1, 2])
|
||||
print(step(e))
|
||||
}
|
||||
4
tests/tool/trace.out
Normal file
4
tests/tool/trace.out
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
trace.fch:6: runtime error: index 3 is out of range (the length is 2)
|
||||
in Enemy.next (trace.fch:6)
|
||||
in step (trace.fch:10)
|
||||
in main (trace.fch:15)
|
||||
Loading…
Reference in a new issue