mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
const, if as a value, """ text over several lines, min/max/abs/clamp, import a/b and import x as y, the fs module
- const NAME = value (numbers, text, other constants; typed: const f32 G = 9.8), also module.NAME
- x := if c { a } else { b }
- """ ... """: the closing quotes' indentation goes from every line
- min, max, abs, clamp for any numbers, unless your own function has the name
- import ui/button (a module in a folder), import path as p
- fs: read_bytes, write_bytes, exists, is_dir, mkdir (with parents), list, remove_dir, data_dir(app)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
parent
d0666cf788
commit
f2d6480749
21 changed files with 688 additions and 17 deletions
|
|
@ -18,7 +18,13 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#ifndef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#include <io.h>
|
||||||
|
#else
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -661,6 +667,147 @@ int finch_try_write_file(const FStr *path, const FStr *text, FStr *err) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- folders (std/fs.fch, through std/finch_fs.h) ----------
|
||||||
|
|
||||||
|
int finch_fs_is_dir(const char *path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
struct _finddata_t d;
|
||||||
|
intptr_t h = _findfirst(path, &d);
|
||||||
|
if (h == -1) return 0;
|
||||||
|
_findclose(h);
|
||||||
|
return (d.attrib & _A_SUBDIR) != 0;
|
||||||
|
#else
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_fs_exists(const char *path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return _access(path, 0) == 0;
|
||||||
|
#else
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static int make_one_dir(const char *p) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return _mkdir(p) == 0 || errno == EEXIST;
|
||||||
|
#else
|
||||||
|
return mkdir(p, 0777) == 0 || errno == EEXIST;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdir -p: the parents too; 1 if the folder is there afterwards
|
||||||
|
int finch_fs_mkdir(const char *path) {
|
||||||
|
size_t n = strlen(path);
|
||||||
|
char *p = finch_alloc((int64_t)n + 1);
|
||||||
|
memcpy(p, path, n + 1);
|
||||||
|
for (size_t i = 1; i < n; i++) {
|
||||||
|
if (p[i] == '/' || p[i] == '\\') {
|
||||||
|
if (p[i - 1] == ':') continue; /* C: is a drive */
|
||||||
|
char c = p[i];
|
||||||
|
p[i] = 0;
|
||||||
|
make_one_dir(p);
|
||||||
|
p[i] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ok = make_one_dir(p) && finch_fs_is_dir(p);
|
||||||
|
free(p);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finch_fs_remove_dir(const char *path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return _rmdir(path) == 0;
|
||||||
|
#else
|
||||||
|
return rmdir(path) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// the names in a folder, one per call ("" at the end), without . and ..
|
||||||
|
typedef struct {
|
||||||
|
#ifdef _WIN32
|
||||||
|
intptr_t h;
|
||||||
|
struct _finddata_t d;
|
||||||
|
int first;
|
||||||
|
#else
|
||||||
|
DIR *d;
|
||||||
|
#endif
|
||||||
|
} FsList;
|
||||||
|
|
||||||
|
void *finch_fs_list_open(const char *path) {
|
||||||
|
FsList *l = finch_zalloc((int64_t)sizeof(FsList));
|
||||||
|
#ifdef _WIN32
|
||||||
|
size_t n = strlen(path);
|
||||||
|
char *pat = finch_alloc((int64_t)n + 3);
|
||||||
|
memcpy(pat, path, n);
|
||||||
|
memcpy(pat + n, "\\*", 3);
|
||||||
|
l->h = _findfirst(pat, &l->d);
|
||||||
|
free(pat);
|
||||||
|
if (l->h == -1) {
|
||||||
|
free(l);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
l->first = 1;
|
||||||
|
#else
|
||||||
|
l->d = opendir(path);
|
||||||
|
if (!l->d) {
|
||||||
|
free(l);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *finch_fs_list_next(void *handle) {
|
||||||
|
FsList *l = handle;
|
||||||
|
for (;;) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!l->first && _findnext(l->h, &l->d) != 0) return "";
|
||||||
|
l->first = 0;
|
||||||
|
const char *name = l->d.name;
|
||||||
|
#else
|
||||||
|
struct dirent *e = readdir(l->d);
|
||||||
|
if (!e) return "";
|
||||||
|
const char *name = e->d_name;
|
||||||
|
#endif
|
||||||
|
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finch_fs_list_close(void *handle) {
|
||||||
|
FsList *l = handle;
|
||||||
|
#ifdef _WIN32
|
||||||
|
_findclose(l->h);
|
||||||
|
#else
|
||||||
|
closedir(l->d);
|
||||||
|
#endif
|
||||||
|
free(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// where a program keeps its own files for this user (saves, settings); "" if unknown
|
||||||
|
// Linux: $XDG_DATA_HOME or ~/.local/share macOS: ~/Library/Application Support Windows: %APPDATA%
|
||||||
|
const char *finch_fs_data_home(void) {
|
||||||
|
static char buf[4096];
|
||||||
|
const char *v;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
v = getenv("APPDATA");
|
||||||
|
snprintf(buf, sizeof buf, "%s", v ? v : "");
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
v = getenv("HOME");
|
||||||
|
if (v) snprintf(buf, sizeof buf, "%s/Library/Application Support", v);
|
||||||
|
else buf[0] = 0;
|
||||||
|
#else
|
||||||
|
v = getenv("XDG_DATA_HOME");
|
||||||
|
if (v && *v) snprintf(buf, sizeof buf, "%s", v);
|
||||||
|
else if ((v = getenv("HOME"))) snprintf(buf, sizeof buf, "%s/.local/share", v);
|
||||||
|
else buf[0] = 0;
|
||||||
|
#endif
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- maps ----------
|
// ---------- maps ----------
|
||||||
// map[K]V = { entries; len; used; cap; index; icap; adds }
|
// 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.
|
// entries: `used` slots of es bytes, each { uint64 hash; K key; V value }, in insertion order.
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,13 @@ struct Node {
|
||||||
struct Program {
|
struct Program {
|
||||||
str module
|
str module
|
||||||
str path
|
str path
|
||||||
[]str imports // Finch modules
|
[]str imports // Finch modules, by the name this file uses: import ui/button -> "button"
|
||||||
|
[]str modules // which module each is: "ui/button" (import path as p: "path")
|
||||||
[]str cimports // C headers: import "stdio.h"
|
[]str cimports // C headers: import "stdio.h"
|
||||||
[]str cwhere // where each was written, for error messages
|
[]str cwhere // where each was written, for error messages
|
||||||
[]str links // link "glfw", link "mine.c"
|
[]str links // link "glfw", link "mine.c"
|
||||||
[]Node structs
|
[]Node structs
|
||||||
[]Node enums
|
[]Node enums
|
||||||
[]Node fns
|
[]Node fns
|
||||||
|
[]Node consts // const NAME = value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
220
src/gen.fch
220
src/gen.fch
|
|
@ -163,6 +163,7 @@ struct Ref {
|
||||||
|
|
||||||
struct Gen {
|
struct Gen {
|
||||||
[]ast.Program progs
|
[]ast.Program progs
|
||||||
|
[]str constBusy // the constants being worked out: a loop between them is an error
|
||||||
[]str files
|
[]str files
|
||||||
[]str head
|
[]str head
|
||||||
[]str out
|
[]str out
|
||||||
|
|
@ -520,7 +521,17 @@ fn progOf(ptr[Gen] g, str module) -> int {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn imports(ptr[Gen] g, str module) -> bool {
|
fn imports(ptr[Gen] g, str module) -> bool {
|
||||||
return g.progs[progOf(g, g.module)].imports.contains(module)
|
return modFor(g, module).len > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// the module a name stands for in this file: button -> "ui/button", p -> "path"; "" if none
|
||||||
|
fn modFor(ptr[Gen] g, str name) -> str {
|
||||||
|
pr := g.progs[progOf(g, g.module)]
|
||||||
|
k := pr.imports.find(name)
|
||||||
|
if k < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pr.modules[k]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
||||||
|
|
@ -567,7 +578,7 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
||||||
if !imports(g, t.module) {
|
if !imports(g, t.module) {
|
||||||
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
||||||
}
|
}
|
||||||
gm = t.module
|
gm = modFor(g, t.module)
|
||||||
}
|
}
|
||||||
return instStruct(g, gm, t.name, args, at)
|
return instStruct(g, gm, t.name, args, at)
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +593,7 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
||||||
if !imports(g, t.module) {
|
if !imports(g, t.module) {
|
||||||
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
||||||
}
|
}
|
||||||
module = t.module
|
module = modFor(g, t.module)
|
||||||
}
|
}
|
||||||
if findEnum(g, module, t.name) >= 0 {
|
if findEnum(g, module, t.name) >= 0 {
|
||||||
return ast.Type(kind: "enum", name: t.name, module: module)
|
return ast.Type(kind: "enum", name: t.name, module: module)
|
||||||
|
|
@ -606,7 +617,7 @@ fn modName(str m) -> str {
|
||||||
if m.len == 0 {
|
if m.len == 0 {
|
||||||
return "main"
|
return "main"
|
||||||
}
|
}
|
||||||
return m
|
return m.replace("/", "$") // ui/button -> ui$button in symbol names
|
||||||
}
|
}
|
||||||
|
|
||||||
fn llty(ptr[Gen] g, ast.Type t) -> str {
|
fn llty(ptr[Gen] g, ast.Type t) -> str {
|
||||||
|
|
@ -2423,6 +2434,9 @@ fn assignStmt(ptr[Gen] g, ast.Node s) {
|
||||||
if root.len > 0 {
|
if root.len > 0 {
|
||||||
id := lookup(g, root)
|
id := lookup(g, root)
|
||||||
if id < 0 && target.kind == "var" {
|
if id < 0 && target.kind == "var" {
|
||||||
|
if findConst(g, g.module, root) >= 0 {
|
||||||
|
fail(g, s, "'" + root + "' is a constant; it can't be changed")
|
||||||
|
}
|
||||||
fail(g, s, "there is no variable named '" + root + "' (create it with " + root + " := ...)")
|
fail(g, s, "there is no variable named '" + root + "' (create it with " + root + " := ...)")
|
||||||
}
|
}
|
||||||
if id >= 0 && varAt(g, id).readonly {
|
if id >= 0 && varAt(g, id).readonly {
|
||||||
|
|
@ -3266,6 +3280,38 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
return Val(ty: T("void"))
|
return Val(ty: T("void"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// x := if c { a } else { b }: both branches give the same type
|
||||||
|
fn ifValue(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
||||||
|
c := coerce(g, expr(g, e.kids[0]), T("bool"), e.kids[0], "the condition")
|
||||||
|
yes := newLabel(g, "ifv.yes")
|
||||||
|
no := newLabel(g, "ifv.no")
|
||||||
|
end := newLabel(g, "ifv.end")
|
||||||
|
condBr(g, c.v, yes, no)
|
||||||
|
label(g, yes)
|
||||||
|
a := exprWant(g, e.kids[1], want)
|
||||||
|
if a.ty.kind == "void" || a.ty.kind == "null" {
|
||||||
|
fail(g, e.kids[1], "this branch has to give a value")
|
||||||
|
}
|
||||||
|
if want.kind != "void" {
|
||||||
|
a = coerce(g, a, want, e.kids[1], "this branch")
|
||||||
|
}
|
||||||
|
t := a.ty
|
||||||
|
av := own(g, a)
|
||||||
|
fromYes := newLabel(g, "ifv.from")
|
||||||
|
br(g, fromYes)
|
||||||
|
label(g, fromYes)
|
||||||
|
br(g, end)
|
||||||
|
label(g, no)
|
||||||
|
b := coerce(g, exprWant(g, e.kids[2], t), t, e.kids[2], "this branch (both branches of the if have to give the same type)")
|
||||||
|
bv := own(g, b)
|
||||||
|
fromNo := newLabel(g, "ifv.from")
|
||||||
|
br(g, fromNo)
|
||||||
|
label(g, fromNo)
|
||||||
|
br(g, end)
|
||||||
|
label(g, end)
|
||||||
|
return Val(v: op(g, "phi " + llty(g, t) + " " + phiList([av, bv], [fromYes, fromNo])), ty: t, fresh: owning(g, t))
|
||||||
|
}
|
||||||
|
|
||||||
fn phiList([]str vals, []str labels) -> str {
|
fn phiList([]str vals, []str labels) -> str {
|
||||||
[]str parts
|
[]str parts
|
||||||
for i in 0..vals.len {
|
for i in 0..vals.len {
|
||||||
|
|
@ -3385,6 +3431,9 @@ fn exprWant(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
||||||
if e.kind == "map" {
|
if e.kind == "map" {
|
||||||
return mapLit(g, e, want)
|
return mapLit(g, e, want)
|
||||||
}
|
}
|
||||||
|
if e.kind == "ifvalue" {
|
||||||
|
return ifValue(g, e, want) // f32 x = if c { 1.0 } else { 0.5 }
|
||||||
|
}
|
||||||
return expr(g, e)
|
return expr(g, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3433,6 +3482,9 @@ fn expr(ptr[Gen] g, ast.Node e) -> Val {
|
||||||
if k == "match" {
|
if k == "match" {
|
||||||
return matchGen(g, e, true)
|
return matchGen(g, e, true)
|
||||||
}
|
}
|
||||||
|
if k == "ifvalue" {
|
||||||
|
return ifValue(g, e, T("void"))
|
||||||
|
}
|
||||||
if k == "try" {
|
if k == "try" {
|
||||||
return tryExpr(g, e)
|
return tryExpr(g, e)
|
||||||
}
|
}
|
||||||
|
|
@ -3840,6 +3892,12 @@ fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref {
|
||||||
}
|
}
|
||||||
return Ref(isPlace: true, pl: Place(addr: "@" + e.name, ty: gt))
|
return Ref(isPlace: true, pl: Place(addr: "@" + e.name, ty: gt))
|
||||||
}
|
}
|
||||||
|
if id < 0 && findConst(g, g.module, e.name) >= 0 {
|
||||||
|
if write {
|
||||||
|
fail(g, e, "'" + e.name + "' is a constant; it can't be changed")
|
||||||
|
}
|
||||||
|
return Ref(isPlace: false, val: constValue(g, g.module, e.name, e))
|
||||||
|
}
|
||||||
if id < 0 {
|
if id < 0 {
|
||||||
if imports(g, e.name) {
|
if imports(g, e.name) {
|
||||||
fail(g, e, "'" + e.name + "' is a module; use something from it, like " + e.name + ".name(...)")
|
fail(g, e, "'" + e.name + "' is a module; use something from it, like " + e.name + ".name(...)")
|
||||||
|
|
@ -3878,13 +3936,76 @@ fn llFloat(str text) -> str {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- constants ----------
|
||||||
|
|
||||||
|
fn findConst(ptr[Gen] g, str module, str name) -> int {
|
||||||
|
pi := progOf(g, module)
|
||||||
|
if pi < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for i in 0..g.progs[pi].consts.len {
|
||||||
|
if g.progs[pi].consts[i].name == name {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constant is worked out where it is used, from the code it was written with: numbers,
|
||||||
|
// text, true/false, chars, other constants, enum variants and operators between them.
|
||||||
|
fn constValue(ptr[Gen] g, str module, str name, ast.Node at) -> Val {
|
||||||
|
pi := progOf(g, module)
|
||||||
|
c := g.progs[pi].consts[findConst(g, module, name)]
|
||||||
|
key := module + "." + name
|
||||||
|
if g.constBusy.contains(key) {
|
||||||
|
fail(g, at, "the constant '" + name + "' is worked out from itself")
|
||||||
|
}
|
||||||
|
checkConst(g, c.kids[0])
|
||||||
|
g.constBusy.push(key)
|
||||||
|
savedScopes := g.scopes
|
||||||
|
savedModule := g.module
|
||||||
|
[]Scope none
|
||||||
|
g.scopes = none
|
||||||
|
pushScope(g)
|
||||||
|
g.module = module
|
||||||
|
v := Val()
|
||||||
|
if c.flag {
|
||||||
|
t := resolve(g, c.ty, c)
|
||||||
|
v = coerce(g, exprWant(g, c.kids[0], t), t, c.kids[0], "the constant '" + name + "'")
|
||||||
|
} else {
|
||||||
|
v = expr(g, c.kids[0])
|
||||||
|
}
|
||||||
|
g.module = savedModule
|
||||||
|
g.scopes = savedScopes
|
||||||
|
g.constBusy.pop()
|
||||||
|
noteRef(g, at, at.col, name.len, "```finch\nconst " + ast.show(v.ty) + " " + name + "\n```", g.files[c.file], c.line, c.col)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkConst(ptr[Gen] g, ast.Node e) {
|
||||||
|
k := e.kind
|
||||||
|
if ["int", "float", "char", "str", "bool", "var", "unary", "binary", "member"].contains(k) {
|
||||||
|
for kid in e.kids {
|
||||||
|
checkConst(g, kid)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k == "call" && numType(e.name).len > 0 {
|
||||||
|
for kid in e.kids {
|
||||||
|
checkConst(g, kid)
|
||||||
|
}
|
||||||
|
return // f32(0.5), u8(255)
|
||||||
|
}
|
||||||
|
fail(g, e, "a constant is made of numbers, text, true/false, chars and other constants (not calls or variables)")
|
||||||
|
}
|
||||||
|
|
||||||
// Which enum does `m.kids[0]` name, if it names one? Color, or shapes.Color. -1 if neither.
|
// Which enum does `m.kids[0]` name, if it names one? Color, or shapes.Color. -1 if neither.
|
||||||
fn enumNamed(ptr[Gen] g, ast.Node n) -> int {
|
fn enumNamed(ptr[Gen] g, ast.Node n) -> int {
|
||||||
if n.kind == "var" && lookup(g, n.name) < 0 {
|
if n.kind == "var" && lookup(g, n.name) < 0 {
|
||||||
return findEnum(g, g.module, n.name)
|
return findEnum(g, g.module, n.name)
|
||||||
}
|
}
|
||||||
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
||||||
return findEnum(g, n.kids[0].name, n.name)
|
return findEnum(g, modFor(g, n.kids[0].name), n.name)
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
@ -3895,7 +4016,7 @@ fn sumNamed(ptr[Gen] g, ast.Node n) -> int {
|
||||||
return findSum(g, g.module, n.name)
|
return findSum(g, g.module, n.name)
|
||||||
}
|
}
|
||||||
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
||||||
return findSum(g, n.kids[0].name, n.name)
|
return findSum(g, modFor(g, n.kids[0].name), n.name)
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
@ -3997,6 +4118,13 @@ fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
||||||
}
|
}
|
||||||
return Ref(isPlace: false, val: enumValue(g, m, ei))
|
return Ref(isPlace: false, val: enumValue(g, m, ei))
|
||||||
}
|
}
|
||||||
|
mod := m.kids[0]
|
||||||
|
if mod.kind == "var" && lookup(g, mod.name) < 0 && imports(g, mod.name) && findConst(g, modFor(g, mod.name), m.name) >= 0 {
|
||||||
|
if write {
|
||||||
|
fail(g, m, "'" + mod.name + "." + m.name + "' is a constant; it can't be changed")
|
||||||
|
}
|
||||||
|
return Ref(isPlace: false, val: constValue(g, modFor(g, mod.name), m.name, m)) // shapes.SIDES
|
||||||
|
}
|
||||||
obj := ref(g, m.kids[0], write)
|
obj := ref(g, m.kids[0], write)
|
||||||
t := obj.val.ty
|
t := obj.val.ty
|
||||||
if obj.isPlace {
|
if obj.isPlace {
|
||||||
|
|
@ -5198,6 +5326,9 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
if si >= 0 {
|
if si >= 0 {
|
||||||
return construct(g, si, c.kids, c.names, c)
|
return construct(g, si, c.kids, c.names, c)
|
||||||
}
|
}
|
||||||
|
if ["min", "max", "abs", "clamp"].contains(n) {
|
||||||
|
return mathCall(g, c) // only when nothing of yours (or C's abs) has the name
|
||||||
|
}
|
||||||
if lookup(g, n) >= 0 {
|
if lookup(g, n) >= 0 {
|
||||||
fail(g, c, "'" + n + "' is a variable, not a function")
|
fail(g, c, "'" + n + "' is a variable, not a function")
|
||||||
}
|
}
|
||||||
|
|
@ -5205,6 +5336,75 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
return Val()
|
return Val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// min(a, b, ...), max(a, b, ...), abs(x), clamp(x, lo, hi) for any numbers
|
||||||
|
fn mathCall(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
|
n := c.name
|
||||||
|
for nm in c.names {
|
||||||
|
if nm.len > 0 {
|
||||||
|
fail(g, c, "named arguments (" + nm + ": ...) are only for creating structs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[]Val vals
|
||||||
|
for a in c.kids {
|
||||||
|
v := expr(g, a)
|
||||||
|
if !isNumber(v.ty) {
|
||||||
|
fail(g, a, n + "(...) is for numbers, but this is " + ast.show(v.ty))
|
||||||
|
}
|
||||||
|
vals.push(v)
|
||||||
|
}
|
||||||
|
if n == "abs" {
|
||||||
|
checkArgs(g, c, 0, 1, n)
|
||||||
|
x := vals[0]
|
||||||
|
if isUnsigned(x.ty) {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
zero := "0"
|
||||||
|
if isFloat(x.ty) {
|
||||||
|
zero = "0.0"
|
||||||
|
}
|
||||||
|
neg := arith(g, "-", Val(v: zero, ty: x.ty, lit: true), x, c)
|
||||||
|
below := compare(g, "<", x, Val(v: zero, ty: x.ty, lit: true), c)
|
||||||
|
return Val(v: op(g, "select i1 " + below.v + ", " + llty(g, x.ty) + " " + neg.v + ", " + llty(g, x.ty) + " " + x.v), ty: x.ty)
|
||||||
|
}
|
||||||
|
if n == "clamp" {
|
||||||
|
checkArgs(g, c, 0, 3, n)
|
||||||
|
low := pick(g, "<", vals[0], vals[2], c) // min(x, hi)
|
||||||
|
return pick(g, ">", low, vals[1], c) // max(that, lo)
|
||||||
|
}
|
||||||
|
if vals.len < 2 {
|
||||||
|
fail(g, c, "'" + n + "' takes two or more numbers")
|
||||||
|
}
|
||||||
|
o := "<"
|
||||||
|
if n == "max" {
|
||||||
|
o = ">"
|
||||||
|
}
|
||||||
|
out := vals[0]
|
||||||
|
for i in 1..vals.len {
|
||||||
|
out = pick(g, o, out, vals[i], c)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
r := b
|
||||||
|
if !unify(g, addr(l), addr(r)) {
|
||||||
|
badOperands(g, o, a, b, at)
|
||||||
|
}
|
||||||
|
if l.lit && r.lit && l.ty.kind == "int" {
|
||||||
|
x := int(l.v) or 0
|
||||||
|
y := int(r.v) or 0
|
||||||
|
if (o == "<") == (x < y) {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
first := compare(g, o, l, r, at)
|
||||||
|
t := llty(g, l.ty)
|
||||||
|
return Val(v: op(g, "select i1 " + first.v + ", " + t + " " + l.v + ", " + t + " " + r.v), ty: l.ty)
|
||||||
|
}
|
||||||
|
|
||||||
fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, str selfRoot) -> Val {
|
fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, str selfRoot) -> Val {
|
||||||
sig := g.sigs[fi]
|
sig := g.sigs[fi]
|
||||||
if sig.module.len == 0 && sig.name == "main" {
|
if sig.module.len == 0 && sig.name == "main" {
|
||||||
|
|
@ -5375,13 +5575,15 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
||||||
if !imports(g, mn) {
|
if !imports(g, mn) {
|
||||||
fail(g, objNode, "there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)")
|
fail(g, objNode, "there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)")
|
||||||
}
|
}
|
||||||
|
shown := mn
|
||||||
|
mn = modFor(g, mn)
|
||||||
fi := findFn(g, mn, m.name)
|
fi := findFn(g, mn, m.name)
|
||||||
if fi >= 0 {
|
if fi >= 0 {
|
||||||
return callFn(g, fi, m, 1, mn + "." + m.name, "", "")
|
return callFn(g, fi, m, 1, shown + "." + m.name, "", "")
|
||||||
}
|
}
|
||||||
mti := findTemplate(g, mn, m.name, "", false)
|
mti := findTemplate(g, mn, m.name, "", false)
|
||||||
if mti >= 0 {
|
if mti >= 0 {
|
||||||
return genericCall(g, m, mti, 1, mn + "." + m.name)
|
return genericCall(g, m, mti, 1, shown + "." + m.name)
|
||||||
}
|
}
|
||||||
mei := findEnum(g, mn, m.name)
|
mei := findEnum(g, mn, m.name)
|
||||||
if mei >= 0 {
|
if mei >= 0 {
|
||||||
|
|
@ -5395,7 +5597,7 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
||||||
}
|
}
|
||||||
return construct(g, si, args, m.names, m)
|
return construct(g, si, args, m.names, m)
|
||||||
}
|
}
|
||||||
fail(g, m, "the module '" + mn + "' has no function or struct named '" + m.name + "'")
|
fail(g, m, "the module '" + shown + "' has no function or struct named '" + m.name + "'")
|
||||||
}
|
}
|
||||||
changing := ["push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"]
|
changing := ["push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"]
|
||||||
r := ref(g, objNode, changing.contains(m.name))
|
r := ref(g, objNode, changing.contains(m.name))
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ fn isHex(char c) -> bool {
|
||||||
|
|
||||||
fn isKeyword(str w) -> bool {
|
fn isKeyword(str w) -> bool {
|
||||||
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
||||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export"]
|
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export", "const"]
|
||||||
return keywords.contains(w)
|
return keywords.contains(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,6 +78,57 @@ fn operator(ptr[Lexer] lx) -> str {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// """ text over several lines """: the line break after the opening quotes is dropped, and so is
|
||||||
|
// the indentation of the closing quotes, from every line (the text can be indented with the code).
|
||||||
|
fn longString(ptr[Lexer] lx, Token t) -> str {
|
||||||
|
for k in 0..3 {
|
||||||
|
advance(lx)
|
||||||
|
}
|
||||||
|
[]str parts
|
||||||
|
while !(peek(lx, 0) == '"' && peek(lx, 1) == '"' && peek(lx, 2) == '"') {
|
||||||
|
if lx.i >= lx.src.len {
|
||||||
|
fail(lx, t.line, t.col, "text is never closed (missing \"\"\")")
|
||||||
|
}
|
||||||
|
ch := peek(lx, 0)
|
||||||
|
advance(lx)
|
||||||
|
if ch == '\\' {
|
||||||
|
e := peek(lx, 0)
|
||||||
|
advance(lx)
|
||||||
|
ch = escape(lx, e)
|
||||||
|
} else if ch == '\r' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts.push(str(ch))
|
||||||
|
}
|
||||||
|
for k in 0..3 {
|
||||||
|
advance(lx)
|
||||||
|
}
|
||||||
|
lx.nl = false // the line breaks inside were text, not the end of a statement
|
||||||
|
body := parts.join("")
|
||||||
|
if body.starts_with("\n") {
|
||||||
|
body = body.sub(1, body.len)
|
||||||
|
}
|
||||||
|
last := body.split("\n")
|
||||||
|
indent := last[last.len - 1]
|
||||||
|
if last.len > 1 && indent.trim().len == 0 {
|
||||||
|
// the closing quotes are on their own line: their indentation goes from every line
|
||||||
|
[]str lines
|
||||||
|
for i in 0..last.len - 1 {
|
||||||
|
line := last[i]
|
||||||
|
if line.starts_with(indent) {
|
||||||
|
line = line.sub(indent.len, line.len)
|
||||||
|
} else if line.trim().len > 0 {
|
||||||
|
fail(lx, t.line, t.col, "a line of this text is indented less than the closing \"\"\"")
|
||||||
|
} else {
|
||||||
|
line = ""
|
||||||
|
}
|
||||||
|
lines.push(line)
|
||||||
|
}
|
||||||
|
return lines.join("\n")
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
fn escape(ptr[Lexer] lx, char e) -> char {
|
fn escape(ptr[Lexer] lx, char e) -> char {
|
||||||
if e == 'n' {
|
if e == 'n' {
|
||||||
return '\n'
|
return '\n'
|
||||||
|
|
@ -185,6 +236,9 @@ fn lex(str src, str path) -> []Token {
|
||||||
if mant.len > 0 {
|
if mant.len > 0 {
|
||||||
t.text = (mant + lx.src.sub(expStart, lx.i)).replace("_", "") // LLVM wants the '.'
|
t.text = (mant + lx.src.sub(expStart, lx.i)).replace("_", "") // LLVM wants the '.'
|
||||||
}
|
}
|
||||||
|
} else if c == '"' && peek(lx, 1) == '"' && peek(lx, 2) == '"' {
|
||||||
|
t.kind = "string"
|
||||||
|
t.text = longString(lx, t)
|
||||||
} else if c == '"' || c == '\'' {
|
} else if c == '"' || c == '\'' {
|
||||||
quote := c
|
quote := c
|
||||||
t.kind = "string"
|
t.kind = "string"
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,9 @@ fn load(ptr[Loader] l, str path, str module) {
|
||||||
prog := parser.parse(lexer.lex(src, path), file, path)
|
prog := parser.parse(lexer.lex(src, path), file, path)
|
||||||
prog.module = module
|
prog.module = module
|
||||||
prog.path = path
|
prog.path = path
|
||||||
imports := prog.imports
|
modules := prog.modules
|
||||||
l.progs.push(prog)
|
l.progs.push(prog)
|
||||||
for name in imports {
|
for name in modules {
|
||||||
if !l.loaded.contains(name) {
|
if !l.loaded.contains(name) {
|
||||||
l.loaded.push(name)
|
l.loaded.push(name)
|
||||||
load(l, findModule(l, path, name), name)
|
load(l, findModule(l, path, name), name)
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,23 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
if !at(p, "ident") {
|
if !at(p, "ident") {
|
||||||
fail(p, t, "expected a module name or a C header after 'import'")
|
fail(p, t, "expected a module name or a C header after 'import'")
|
||||||
}
|
}
|
||||||
prog.imports.push(next(p).text)
|
// import ui/button (a folder), import path as p (another name here)
|
||||||
|
key := next(p).text
|
||||||
|
name := key
|
||||||
|
while at(p, "/") && !cur(p).nl {
|
||||||
|
next(p)
|
||||||
|
name = expect(p, "ident", "a module name after '/'").text
|
||||||
|
key = key + "/" + name
|
||||||
|
}
|
||||||
|
if at(p, "ident") && cur(p).text == "as" && !cur(p).nl {
|
||||||
|
next(p)
|
||||||
|
name = expect(p, "ident", "the name to use for the module, like import path as p").text
|
||||||
|
}
|
||||||
|
if prog.imports.contains(name) {
|
||||||
|
fail(p, t, "the name '" + name + "' is already used by another import (pick one with: import " + key + " as other)")
|
||||||
|
}
|
||||||
|
prog.imports.push(name)
|
||||||
|
prog.modules.push(key)
|
||||||
}
|
}
|
||||||
} else if at(p, "link") {
|
} else if at(p, "link") {
|
||||||
lt := next(p)
|
lt := next(p)
|
||||||
|
|
@ -248,6 +264,8 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
prog.links.push(lib)
|
prog.links.push(lib)
|
||||||
} else if at(p, "struct") {
|
} else if at(p, "struct") {
|
||||||
prog.structs.push(parseStruct(p))
|
prog.structs.push(parseStruct(p))
|
||||||
|
} else if at(p, "const") {
|
||||||
|
prog.consts.push(parseConst(p))
|
||||||
} else if at(p, "enum") {
|
} else if at(p, "enum") {
|
||||||
e := parseEnum(p)
|
e := parseEnum(p)
|
||||||
prog.enums.push(e)
|
prog.enums.push(e)
|
||||||
|
|
@ -261,6 +279,24 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const WIDTH = 640 const f32 GRAVITY = 9.8 const TITLE = "Game"
|
||||||
|
fn parseConst(ptr[Parser] p) -> ast.Node {
|
||||||
|
c := node(p, "const")
|
||||||
|
expect(p, "const", "'const'")
|
||||||
|
if !(at(p, "ident") && peekKind(p, 1) == "=") {
|
||||||
|
c.ty = parseType(p)
|
||||||
|
c.flag = true
|
||||||
|
}
|
||||||
|
t := expect(p, "ident", "the constant's name, like const WIDTH = 640")
|
||||||
|
c.name = t.text
|
||||||
|
c.line = t.line
|
||||||
|
c.col = t.col
|
||||||
|
expect(p, "=", "'=' and its value, like const WIDTH = 640")
|
||||||
|
c.kids.push(parseExpr(p))
|
||||||
|
endOfStatement(p)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// enum Color { Red, Green, Blue } enum Status { Ok = 200, NotFound = 404 }
|
// enum Color { Red, Green, Blue } enum Status { Ok = 200, NotFound = 404 }
|
||||||
// The variants are numbered from 0 (or from the last number given) and are written Color.Red.
|
// The variants are numbered from 0 (or from the last number given) and are written Color.Red.
|
||||||
// A variant can hold values: enum Shape { Circle(float), Rect(float, float), Point } (e.flag is then true).
|
// A variant can hold values: enum Shape { Circle(float), Rect(float, float), Point } (e.flag is then true).
|
||||||
|
|
@ -433,7 +469,7 @@ fn parseFn(ptr[Parser] p) -> ast.Node {
|
||||||
if at(p, "ident") && isTypeName(cur(p).text) {
|
if at(p, "ident") && isTypeName(cur(p).text) {
|
||||||
fail(p, cur(p), "functions start with 'fn', like: fn add(int a, int b) -> int { ... }")
|
fail(p, cur(p), "functions start with 'fn', like: fn add(int a, int b) -> int { ... }")
|
||||||
}
|
}
|
||||||
expect(p, "fn", "'fn', 'struct' or 'import'")
|
expect(p, "fn", "'fn', 'struct', 'enum', 'const' or 'import'")
|
||||||
f.name = expect(p, "ident", "a function name").text
|
f.name = expect(p, "ident", "a function name").text
|
||||||
if at(p, "[") {
|
if at(p, "[") {
|
||||||
f.tparams = parseTParams(p) // fn max[T](...) or the struct's, in fn Stack[T].push(...)
|
f.tparams = parseTParams(p) // fn max[T](...) or the struct's, in fn Stack[T].push(...)
|
||||||
|
|
@ -612,6 +648,33 @@ fn parseIf(ptr[Parser] p) -> ast.Node {
|
||||||
|
|
||||||
// ---------- expressions ----------
|
// ---------- expressions ----------
|
||||||
|
|
||||||
|
// if as a value: x := if a > b { a } else { b } (each branch is one value; the else is needed)
|
||||||
|
fn parseIfValue(ptr[Parser] p) -> ast.Node {
|
||||||
|
e := node(p, "ifvalue")
|
||||||
|
expect(p, "if", "'if'")
|
||||||
|
e.kids.push(parseExpr(p))
|
||||||
|
e.kids.push(branchValue(p))
|
||||||
|
if !accept(p, "else") {
|
||||||
|
fail(p, cur(p), "an if that gives a value needs an else: if c { a } else { b }")
|
||||||
|
}
|
||||||
|
if at(p, "if") {
|
||||||
|
e.kids.push(parseIfValue(p))
|
||||||
|
} else {
|
||||||
|
e.kids.push(branchValue(p))
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branchValue(ptr[Parser] p) -> ast.Node {
|
||||||
|
expect(p, "{", "'{'")
|
||||||
|
saved := p.depth
|
||||||
|
p.depth = 1
|
||||||
|
v := parseExpr(p)
|
||||||
|
p.depth = saved
|
||||||
|
expect(p, "}", "'}' (a branch of an if that gives a value holds just that value)")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
fn binaryLevel(str op) -> int {
|
fn binaryLevel(str op) -> int {
|
||||||
if op == "||" {
|
if op == "||" {
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -769,6 +832,9 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
|
||||||
if k == "match" {
|
if k == "match" {
|
||||||
return parseMatch(p, true)
|
return parseMatch(p, true)
|
||||||
}
|
}
|
||||||
|
if k == "if" {
|
||||||
|
return parseIfValue(p)
|
||||||
|
}
|
||||||
if k == "[" {
|
if k == "[" {
|
||||||
next(p)
|
next(p)
|
||||||
e.kind = "array"
|
e.kind = "array"
|
||||||
|
|
|
||||||
9
std/finch_fs.h
Normal file
9
std/finch_fs.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* The runtime's folder functions, for std/fs.fch (they are in runtime/finch_rt.c, linked into every program). */
|
||||||
|
int finch_fs_is_dir(const char *path);
|
||||||
|
int finch_fs_exists(const char *path);
|
||||||
|
int finch_fs_mkdir(const char *path);
|
||||||
|
int finch_fs_remove_dir(const char *path);
|
||||||
|
void *finch_fs_list_open(const char *path);
|
||||||
|
const char *finch_fs_list_next(void *list);
|
||||||
|
void finch_fs_list_close(void *list);
|
||||||
|
const char *finch_fs_data_home(void);
|
||||||
70
std/fs.fch
Normal file
70
std/fs.fch
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Files and folders: bytes, folders, listing, and a place for a program's own files.
|
||||||
|
//
|
||||||
|
// import fs
|
||||||
|
// data := try fs.read_bytes("atlas.png") the whole file as []u8 (any bytes, NUL included)
|
||||||
|
// try fs.write_bytes("out.bin", data)
|
||||||
|
// try fs.mkdir("saves/slot1") with the folders above it
|
||||||
|
// names := try fs.list("saves") the names in a folder, sorted
|
||||||
|
// fs.exists(p) fs.is_dir(p) try fs.remove_dir(p)
|
||||||
|
// dir := try fs.data_dir("MyGame") a folder for this user's saves, made if needed
|
||||||
|
import "finch_fs.h"
|
||||||
|
|
||||||
|
fn read_bytes(str path) -> []u8! {
|
||||||
|
text := try read_file(path)
|
||||||
|
return text.bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_bytes(str path, []u8 data) -> ! {
|
||||||
|
try write_file(path, str(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(str path) -> bool {
|
||||||
|
return finch_fs_exists(path) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dir(str path) -> bool {
|
||||||
|
return finch_fs_is_dir(path) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkdir(str path) -> ! {
|
||||||
|
if finch_fs_mkdir(path) == 0 {
|
||||||
|
return error("can't make the folder \"" + path + "\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only an empty folder
|
||||||
|
fn remove_dir(str path) -> ! {
|
||||||
|
if finch_fs_remove_dir(path) == 0 {
|
||||||
|
return error("can't remove the folder \"" + path + "\" (is it empty?)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(str path) -> []str! {
|
||||||
|
l := finch_fs_list_open(path)
|
||||||
|
if l == null {
|
||||||
|
return error("can't list the folder \"" + path + "\"")
|
||||||
|
}
|
||||||
|
[]str names
|
||||||
|
while true {
|
||||||
|
name := finch_fs_list_next(l)
|
||||||
|
if name.len == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
names.push(name)
|
||||||
|
}
|
||||||
|
finch_fs_list_close(l)
|
||||||
|
names.sort()
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// The folder where a program keeps this user's files (saves, settings), made if it isn't there:
|
||||||
|
// ~/.local/share/<app> on Linux, ~/Library/Application Support/<app> on macOS, %APPDATA%\<app> on Windows.
|
||||||
|
fn data_dir(str app) -> str! {
|
||||||
|
home := finch_fs_data_home()
|
||||||
|
if home.len == 0 {
|
||||||
|
return error("this system doesn't say where programs keep their files")
|
||||||
|
}
|
||||||
|
dir := home + "/" + app
|
||||||
|
try mkdir(dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
5
tests/new/fail/const_assign.fch
Normal file
5
tests/new/fail/const_assign.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: is a constant; it can't be changed
|
||||||
|
const W = 3
|
||||||
|
fn main() {
|
||||||
|
W = 4
|
||||||
|
}
|
||||||
6
tests/new/fail/const_call.fch
Normal file
6
tests/new/fail/const_call.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: a constant is made of numbers, text
|
||||||
|
fn f() -> int { return 1 }
|
||||||
|
const W = f()
|
||||||
|
fn main() {
|
||||||
|
print(W)
|
||||||
|
}
|
||||||
6
tests/new/fail/const_loop.fch
Normal file
6
tests/new/fail/const_loop.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: is worked out from itself
|
||||||
|
const A = B + 1
|
||||||
|
const B = A
|
||||||
|
fn main() {
|
||||||
|
print(A)
|
||||||
|
}
|
||||||
4
tests/new/fail/ifvalue_else.fch
Normal file
4
tests/new/fail/ifvalue_else.fch
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: an if that gives a value needs an else
|
||||||
|
fn main() {
|
||||||
|
x := if true { 1 }
|
||||||
|
}
|
||||||
4
tests/new/fail/ifvalue_types.fch
Normal file
4
tests/new/fail/ifvalue_types.fch
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: both branches of the if have to give the same type
|
||||||
|
fn main() {
|
||||||
|
x := if true { 1 } else { "one" }
|
||||||
|
}
|
||||||
5
tests/new/fail/import_clash.fch
Normal file
5
tests/new/fail/import_clash.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: is already used by another import
|
||||||
|
import path
|
||||||
|
import time as path
|
||||||
|
fn main() {
|
||||||
|
}
|
||||||
6
tests/new/fail/longstr_indent.fch
Normal file
6
tests/new/fail/longstr_indent.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: is indented less than the closing
|
||||||
|
fn main() {
|
||||||
|
s := """
|
||||||
|
a
|
||||||
|
"""
|
||||||
|
}
|
||||||
33
tests/new/lang_additions.fch
Normal file
33
tests/new/lang_additions.fch
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
// const, if as a value, min/max/abs/clamp, import a/b and import x as y, """ text
|
||||||
|
import ui/button
|
||||||
|
import path as p
|
||||||
|
import ui/button as b2
|
||||||
|
|
||||||
|
const W = 640
|
||||||
|
const H = W / 2
|
||||||
|
const f32 G = 9.8
|
||||||
|
const TITLE = "Game " + "One"
|
||||||
|
const NEG = -W
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(W, H, G, TITLE, NEG, button.SIZE)
|
||||||
|
bt := button.make("ok")
|
||||||
|
button.Button other
|
||||||
|
print(bt.label, bt.w, other.w, b2.make("x").w)
|
||||||
|
print(p.base("/a/b.txt"))
|
||||||
|
x := 7
|
||||||
|
v := if x > 5 { "big" } else if x > 2 { "mid" } else { "small" }
|
||||||
|
f32 s = if x > 5 { 1.5 } else { 0.5 }
|
||||||
|
print(v, s, if x == 7 { x * 2 } else { 0 })
|
||||||
|
print(min(3, 9), max(2.5, 1), abs(-4), abs(-2.5), clamp(15, 0, 10), clamp(-3, 0, 10), min(5, 2, 8))
|
||||||
|
f32 sp = 2.0
|
||||||
|
print(clamp(sp * 3.0, 0.0, 5.0), max(u8(3), u8(200)))
|
||||||
|
shader := """
|
||||||
|
#version 330
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(0.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
print(shader)
|
||||||
|
print("""one "quoted" line""")
|
||||||
|
}
|
||||||
11
tests/new/lang_additions.out
Normal file
11
tests/new/lang_additions.out
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
640 320 9.8 Game One -640 3
|
||||||
|
ok 30 30 30
|
||||||
|
b.txt
|
||||||
|
big 1.5 14
|
||||||
|
3 2.5 4 2.5 10 0 2
|
||||||
|
5 200
|
||||||
|
#version 330
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(0.0);
|
||||||
|
}
|
||||||
|
one "quoted" line
|
||||||
23
tests/new/std_fs.fch
Normal file
23
tests/new/std_fs.fch
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// the fs module: bytes with NUL in them, folders, listing
|
||||||
|
import fs
|
||||||
|
|
||||||
|
fn main() -> ! {
|
||||||
|
root := "fs_test_dir"
|
||||||
|
try fs.mkdir(root + "/a/b")
|
||||||
|
print(fs.exists(root), fs.is_dir(root + "/a/b"), fs.is_dir("nope"))
|
||||||
|
data := [u8(0), u8(255), u8(13), u8(10), u8(65)]
|
||||||
|
try fs.write_bytes(root + "/a/blob.bin", data)
|
||||||
|
back := try fs.read_bytes(root + "/a/blob.bin")
|
||||||
|
print(back.len, back[0], back[1], back[4], str(back) == str(data))
|
||||||
|
write_file(root + "/z.txt", "z")
|
||||||
|
print(try fs.list(root))
|
||||||
|
print(try fs.list(root + "/a"))
|
||||||
|
print(fs.list("no_such_dir") or ["(none)"])
|
||||||
|
delete_file(root + "/a/blob.bin")
|
||||||
|
delete_file(root + "/z.txt")
|
||||||
|
try fs.remove_dir(root + "/a/b")
|
||||||
|
try fs.remove_dir(root + "/a")
|
||||||
|
try fs.remove_dir(root)
|
||||||
|
print(fs.exists(root))
|
||||||
|
fs.remove_dir(root) or { print("gone:", err) }
|
||||||
|
}
|
||||||
7
tests/new/std_fs.out
Normal file
7
tests/new/std_fs.out
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
true true false
|
||||||
|
5 0 255 65 true
|
||||||
|
["a", "z.txt"]
|
||||||
|
["b", "blob.bin"]
|
||||||
|
["(none)"]
|
||||||
|
false
|
||||||
|
gone: can't remove the folder "fs_test_dir" (is it empty?)
|
||||||
7
tests/new/ui/button.fch
Normal file
7
tests/new/ui/button.fch
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// a module in a folder, for tests/new/lang_additions.fch (import ui/button)
|
||||||
|
const SIZE = 3
|
||||||
|
struct Button {
|
||||||
|
str label
|
||||||
|
int w = SIZE * 10
|
||||||
|
}
|
||||||
|
fn make(str l) -> Button { return Button(label: l) }
|
||||||
Loading…
Reference in a new issue