Overhaul of Path so it uses root and array of components instead of

stringly typed
This commit is contained in:
Bruce Hill 2025-03-15 14:22:11 -04:00
parent 7a26535013
commit f51acef40e
20 changed files with 582 additions and 431 deletions

1
ast.c
View File

@ -108,6 +108,7 @@ CORD ast_to_xml(ast_t *ast)
T(Num, "<Num>%g</Num>", data.n)
T(TextLiteral, "%r", xml_escape(data.cord))
T(TextJoin, "<Text%r>%r</Text>", data.lang ? CORD_all(" lang=\"", data.lang, "\"") : CORD_EMPTY, ast_list_to_xml(data.children))
T(Path, "<Path>%s</Path>", data.path)
T(Declare, "<Declare var=\"%r\">%r</Declare>", ast_to_xml(data.var), ast_to_xml(data.value))
T(Assign, "<Assign><targets>%r</targets><values>%r</values></Assign>", ast_list_to_xml(data.targets), ast_list_to_xml(data.values))
T(BinaryOp, "<BinaryOp op=\"%r\">%r %r</BinaryOp>", xml_escape(OP_NAMES[data.op]), ast_to_xml(data.lhs), ast_to_xml(data.rhs))

4
ast.h
View File

@ -124,6 +124,7 @@ typedef enum {
None, Bool, Var,
Int, Num,
TextLiteral, TextJoin, PrintStatement,
Path,
Declare, Assign,
BinaryOp, UpdateAssign,
Not, Negative, HeapAllocate, StackReference, Mutexed, Holding,
@ -175,6 +176,9 @@ struct ast_s {
const char *lang;
ast_list_t *children;
} TextJoin;
struct {
const char *path;
} Path;
struct {
ast_list_t *to_print;
} PrintStatement;

View File

@ -38,7 +38,7 @@ static CORD compile_string_literal(CORD literal);
CORD promote_to_optional(type_t *t, CORD code)
{
if (t == THREAD_TYPE) {
if (t == THREAD_TYPE || t == PATH_TYPE || t->tag == MomentType) {
return code;
} else if (t->tag == IntType) {
switch (Match(t, IntType)->bits) {
@ -502,6 +502,7 @@ CORD compile_type(type_t *t)
if (t == THREAD_TYPE) return "Thread_t";
else if (t == RNG_TYPE) return "RNG_t";
else if (t == MATCH_TYPE) return "Match_t";
else if (t == PATH_TYPE) return "Path_t";
switch (t->tag) {
case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type");
@ -522,8 +523,6 @@ CORD compile_type(type_t *t)
return "Text_t";
else if (streq(text->lang, "Pattern"))
return "Pattern_t";
else if (streq(text->lang, "Path"))
return "Path_t";
else if (streq(text->lang, "Shell"))
return "Shell_t";
else
@ -570,6 +569,8 @@ CORD compile_type(type_t *t)
return "Thread_t";
if (nonnull == MATCH_TYPE)
return "OptionalMatch_t";
if (nonnull == PATH_TYPE)
return "OptionalPath_t";
auto s = Match(nonnull, StructType);
return CORD_all(namespace_prefix(s->env, s->env->namespace->parent), "$Optional", s->name, "$$type");
}
@ -682,7 +683,7 @@ CORD optional_into_nonnone(type_t *t, CORD value)
case IntType:
return CORD_all(value, ".i");
case StructType:
if (t == THREAD_TYPE || t == MATCH_TYPE)
if (t == THREAD_TYPE || t == MATCH_TYPE || t == PATH_TYPE)
return value;
return CORD_all(value, ".value");
default:
@ -698,6 +699,8 @@ CORD check_none(type_t *t, CORD value)
return CORD_all("(", value, " == NULL)");
else if (t == MATCH_TYPE)
return CORD_all("((", value, ").index.small == 0)");
else if (t == PATH_TYPE)
return CORD_all("((", value, ").root == PATH_NONE)");
else if (t->tag == BigIntType)
return CORD_all("((", value, ").small == 0)");
else if (t->tag == ClosureType)
@ -2191,6 +2194,7 @@ CORD compile_none(type_t *t)
t = Match(t, OptionalType)->type;
if (t == THREAD_TYPE) return "NULL";
else if (t == PATH_TYPE) return "NONE_PATH";
switch (t->tag) {
case BigIntType: return "NONE_INT";
@ -2621,11 +2625,10 @@ CORD compile(env_t *env, ast_t *ast)
code_err(ast, "The 'xor' operator isn't supported between %T and %T values", lhs_t, rhs_t);
}
case BINOP_CONCAT: {
if (operand_t == PATH_TYPE)
return CORD_all("Path$concat(", lhs, ", ", rhs, ")");
switch (operand_t->tag) {
case TextType: {
const char *lang = Match(operand_t, TextType)->lang;
if (streq(lang, "Path"))
return CORD_all("Path$concat(", lhs, ", ", rhs, ")");
return CORD_all("Text$concat(", lhs, ", ", rhs, ")");
}
case ArrayType: {
@ -2660,7 +2663,7 @@ CORD compile(env_t *env, ast_t *ast)
CORD lang_constructor;
if (!lang || streq(lang, "Text"))
lang_constructor = "Text";
else if (streq(lang, "Pattern") || streq(lang, "Path") || streq(lang, "Shell"))
else if (streq(lang, "Pattern") || streq(lang, "Shell"))
lang_constructor = lang;
else
lang_constructor = CORD_all(namespace_prefix(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent), lang);
@ -2704,6 +2707,9 @@ CORD compile(env_t *env, ast_t *ast)
return code;
}
}
case Path: {
return CORD_all("Path(", compile_string_literal(Match(ast, Path)->path), ")");
}
case Block: {
ast_list_t *stmts = Match(ast, Block)->statements;
if (stmts && !stmts->next)
@ -3836,8 +3842,9 @@ CORD compile(env_t *env, ast_t *ast)
CORD compile_type_info(type_t *t)
{
if (t == THREAD_TYPE) return "&Thread$info";
else if (t == MATCH_TYPE) return "&Match$info";
else if (t == RNG_TYPE) return "&RNG$info";
else if (t == MATCH_TYPE) return "&Match$info";
else if (t == PATH_TYPE) return "&Path$info";
switch (t->tag) {
case BoolType: case ByteType: case IntType: case BigIntType: case NumType: case CStringType: case MomentType:
@ -3850,8 +3857,6 @@ CORD compile_type_info(type_t *t)
return "&Pattern$info";
else if (streq(text->lang, "Shell"))
return "&Shell$info";
else if (streq(text->lang, "Path"))
return "&Path$info";
return CORD_all("(&", namespace_prefix(text->env, text->env->namespace->parent), text->lang, "$$info)");
}
case StructType: {

View File

@ -14,6 +14,7 @@
type_t *TEXT_TYPE = NULL;
type_t *MATCH_TYPE = NULL;
type_t *RNG_TYPE = NULL;
public type_t *PATH_TYPE = NULL;
public type_t *THREAD_TYPE = NULL;
env_t *new_compilation_unit(CORD libname)
@ -79,6 +80,14 @@ env_t *new_compilation_unit(CORD libname)
.next=new(arg_t, .name="captures", .type=Type(ArrayType, .item_type=TEXT_TYPE)))));
}
{
env_t *path_env = namespace_env(env, "Path");
PATH_TYPE = Type(
StructType, .name="Path", .env=path_env,
.fields=new(arg_t, .name="root", .type=Type(IntType, .bits=TYPE_IBITS32),
.next=new(arg_t, .name="components", .type=Type(ArrayType, .item_type=TEXT_TYPE))));
}
{
env_t *thread_env = namespace_env(env, "Thread");
THREAD_TYPE = Type(StructType, .name="Thread", .env=thread_env, .opaque=true);
@ -324,12 +333,15 @@ env_t *new_compilation_unit(CORD libname)
{"unix_timestamp", "Moment$unix_timestamp", "func(moment:Moment -> Int64)"},
{"year", "Moment$year", "func(moment:Moment,timezone=none:Text -> Int)"},
)},
{"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t,
{"Path", PATH_TYPE, "Path_t", "Path$info", TypedArray(ns_entry_t,
{"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"},
{"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"},
{"base_name", "Path$base_name", "func(path:Path -> Text)"},
{"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"},
{"child", "Path$with_component", "func(path:Path, child:Text -> Path)"},
{"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
{"components", "Path$components", "func(path:Path -> [Text])"},
{"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"},
{"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"},
{"escape_int", "Int$value_as_text", "func(i:Int -> Path)"},
{"escape_path", "Path$escape_path", "func(path:Path -> Path)"},
@ -337,6 +349,7 @@ env_t *new_compilation_unit(CORD libname)
{"exists", "Path$exists", "func(path:Path -> Bool)"},
{"extension", "Path$extension", "func(path:Path, full=yes -> Text)"},
{"files", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
{"from_components", "Path$from_components", "func(components:[Text] -> Path)"},
{"glob", "Path$glob", "func(path:Path -> [Path])"},
{"is_directory", "Path$is_directory", "func(path:Path, follow_symlinks=yes -> Bool)"},
{"is_file", "Path$is_file", "func(path:Path, follow_symlinks=yes -> Bool)"},
@ -347,6 +360,7 @@ env_t *new_compilation_unit(CORD libname)
{"read", "Path$read", "func(path:Path -> Text?)"},
{"read_bytes", "Path$read_bytes", "func(path:Path, limit=none:Int -> [Byte]?)"},
{"relative", "Path$relative", "func(path:Path, relative_to=(./) -> Path)"},
{"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"},
{"remove", "Path$remove", "func(path:Path, ignore_missing=no)"},
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"},
{"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
@ -359,14 +373,6 @@ env_t *new_compilation_unit(CORD libname)
{"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Moment?)"},
{"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
{"changed", "Path$changed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
// Text methods:
{"ends_with", "Text$ends_with", "func(path:Path, suffix:Text -> Bool)"},
{"has", "Text$has", "func(path:Path, pattern:Pattern -> Bool)"},
{"matches", "Text$matches", "func(path:Path, pattern:Pattern -> [Text]?)"},
{"replace", "Text$replace", "func(path:Path, pattern:Pattern, replacement:Text, backref=$/\\/, recursive=yes -> Path)"},
{"replace_all", "Text$replace_all", "func(path:Path, replacements:{Pattern,Text}, backref=$/\\/, recursive=yes -> Path)"},
{"starts_with", "Text$starts_with", "func(path:Path, prefix:Text -> Bool)"},
)},
// RNG must come after Path so we can read bytes from /dev/urandom
{"RNG", RNG_TYPE, "RNG_t", "RNG", TypedArray(ns_entry_t,
@ -411,7 +417,7 @@ env_t *new_compilation_unit(CORD libname)
{"from_c_string", "Text$from_str", "func(str:CString -> Text?)"},
{"from_codepoint_names", "Text$from_codepoint_names", "func(codepoint_names:[Text] -> Text?)"},
{"from_codepoints", "Text$from_codepoints", "func(codepoints:[Int32] -> Text)"},
{"from_text", "Path$cleanup", "func(text:Text -> Path)"},
{"from_text", "Path$from_text", "func(text:Text -> Path)"},
{"has", "Text$has", "func(text:Text, pattern:Pattern -> Bool)"},
{"join", "Text$join", "func(glue:Text, pieces:[Text] -> Text)"},
{"left_pad", "Text$left_pad", "func(text:Text, count:Int, pad=\" \" -> Text)"},
@ -595,9 +601,8 @@ env_t *new_compilation_unit(CORD libname)
set_binding(namespace_env(env, "Path"), "from_text",
Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE),
.ret=Type(TextType, .lang="Path", .env=namespace_env(env, "Path"))),
"Path$cleanup");
.ret=PATH_TYPE),
"Path$from_text");
set_binding(namespace_env(env, "Pattern"), "from_text",
Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE),

View File

@ -75,6 +75,7 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name);
extern type_t *TEXT_TYPE;
extern type_t *MATCH_TYPE;
extern type_t *RNG_TYPE;
extern type_t *PATH_TYPE;
extern type_t *THREAD_TYPE;
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -7,7 +7,7 @@ _HELP := "
"
func parse_ini(path:Path -> {Text,{Text,Text}}):
text := path:read() or exit("Could not read INI file: $\[31;1]$(path.text)$\[]")
text := path:read() or exit("Could not read INI file: $\[31;1]$(path)$\[]")
sections := @{:Text,@{Text,Text}}
current_section := @{:Text,Text}

View File

@ -13,7 +13,7 @@ func find_urls(path:Path -> [Text]):
if path:is_directory():
for f in path:children():
urls:insert_all(find_urls(f))
else if path:is_file() and path:ends_with(".tm"):
else if path:is_file() and path:extension() == ".tm":
for line in path:by_line()!:
if m := line:matches($/use{space}{url}/) or line:matches($/{id}{space}:={space}use{space}{url}/):
urls:insert(m[-1])

View File

@ -36,7 +36,7 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency,{Dependen
dir := (~/.local/share/tomo/installed/$module)
module_deps := @{:Dependency}
visited := @{:Path}
unvisited := @{f:resolved() for f in dir:files() if f:ends_with(".tm")}
unvisited := @{f:resolved() for f in dir:files() if f:extension() == ".tm"}
while unvisited.length > 0:
file := unvisited.items[-1]
unvisited:remove(file)
@ -66,9 +66,9 @@ func _printable_name(dep:Dependency -> Text):
is File(f):
f = f:relative()
if f:exists():
return "$(f.text)"
return Text(f)
else:
return "$(\x1b)[31;1m$(f.text) (not found)$(\x1b)[m"
return "$(\x1b)[31;1m$(f) (not found)$(\x1b)[m"
func _draw_tree(dep:Dependency, dependencies:{Dependency,{Dependency}}, already_printed:@{Dependency}, prefix="", is_last=yes):
if already_printed:has(dep):

View File

@ -82,7 +82,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN
files = [(/dev/stdin)]
for file in files:
text := file:read() or exit("Could not read file: $(file.text)")
text := file:read() or exit("Could not read file: $file")
if rewrap:
text = unwrap(text)

86
parse.c
View File

@ -1423,73 +1423,43 @@ PARSER(parse_path) {
// "(" ("~/" / "./" / "../" / "/") ... ")"
const char *start = pos;
if (!(match(&pos, "(~/")
|| match(&pos, "(./")
|| match(&pos, "(../")
|| match(&pos, "(/")))
if (!match(&pos, "("))
return NULL;
const char *chunk_start = start + 1;
ast_list_t *chunks = NULL;
CORD chunk_text = CORD_EMPTY;
if (!(*pos == '~' || *pos == '.' || *pos == '/'))
return NULL;
const char *path_start = pos;
size_t len = 1;
int paren_depth = 1;
while (pos < ctx->file->text + ctx->file->len) {
switch (*pos) {
case '\\': {
++pos;
chunk_text = CORD_asprintf("%r%.*s%c", chunk_text, (size_t)(pos - chunk_start), chunk_start, *pos);
++pos;
chunk_start = pos;
while (pos + len < ctx->file->text + ctx->file->len - 1) {
if (pos[len] == '\\') {
len += 2;
continue;
}
case '$': {
const char *interp_start = pos;
if (pos > chunk_start)
chunk_text = CORD_asprintf("%r%.*s", chunk_text, (size_t)(pos - chunk_start), chunk_start);
if (chunk_text) {
ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .cord=chunk_text);
chunks = new(ast_list_t, .ast=literal, .next=chunks);
chunk_text = CORD_EMPTY;
}
++pos;
if (*pos == ' ' || *pos == '\t')
parser_err(ctx, pos, pos+1, "Whitespace is not allowed before an interpolation here");
ast_t *interp = expect(ctx, interp_start, &pos, parse_term_no_suffix, "I expected an interpolation term here");
chunks = new(ast_list_t, .ast=interp, .next=chunks);
chunk_start = pos;
continue;
}
case '(': {
} else if (pos[len] == '(') {
paren_depth += 1;
++pos;
continue;
}
case ')': {
} else if (pos[len] == ')') {
paren_depth -= 1;
if (paren_depth == 0)
goto end_of_path;
++pos;
continue;
if (paren_depth <= 0) break;
} else if (pos[len] == '\r' || pos[len] == '\n') {
parser_err(ctx, path_start, &pos[len-1], "This path was not closed");
}
default: ++pos; continue;
len += 1;
}
pos += len + 1;
char *path = heap_strf("%.*s", (int)len, path_start);
for (char *src = path, *dest = path; ; ) {
if (src[0] == '\\') {
*(dest++) = src[1];
src += 2;
} else if (*src) {
*(dest++) = *(src++);
} else {
*(dest++) = '\0';
break;
}
}
end_of_path:;
if (pos > chunk_start)
chunk_text = CORD_asprintf("%r%.*s", chunk_text, (size_t)(pos - chunk_start), chunk_start);
if (chunk_text != CORD_EMPTY) {
ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .cord=chunk_text);
chunks = new(ast_list_t, .ast=literal, .next=chunks);
}
expect_closing(ctx, &pos, ")", "I was expecting a ')' to finish this path");
REVERSE_LIST(chunks);
return NewAST(ctx->file, start, pos, TextJoin, .lang="Path", .children=chunks);
return NewAST(ctx->file, start, pos, Path, .path=path);
}
PARSER(parse_pass) {

View File

@ -96,6 +96,12 @@ typedef struct Text_s {
#define Pattern_t Text_t
#define OptionalPattern_t Text_t
typedef struct {
enum { PATH_NONE, PATH_RELATIVE, PATH_ROOT, PATH_HOME } root;
Array_t components;
} Path_t;
#define OptionalPath_t Path_t
typedef struct timeval Moment_t;
#define OptionalMoment_t Moment_t

View File

@ -12,6 +12,7 @@
Text_t Moment$as_text(const void *moment, bool colorize, const TypeInfo_t *type);
PUREFUNC int32_t Moment$compare(const void *a, const void *b, const TypeInfo_t *type);
CONSTFUNC public bool Moment$is_none(const void *m, const TypeInfo_t*);
Moment_t Moment$now(void);
Moment_t Moment$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second, OptionalText_t timezone);
Moment_t Moment$after(Moment_t moment, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, OptionalText_t timezone);

View File

@ -23,6 +23,7 @@
#define NONE_CLOSURE ((OptionalClosure_t){.fn=NULL})
#define NONE_TEXT ((OptionalText_t){.length=-1})
#define NONE_MOMENT ((OptionalMoment_t){.tv_usec=-1})
#define NONE_PATH ((Path_t){.root=PATH_NONE})
PUREFUNC bool is_null(const void *obj, const TypeInfo_t *non_optional_type);
PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type);

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@ -23,145 +24,154 @@
#include "types.h"
#include "util.h"
PUREFUNC public Path_t Path$escape_text(Text_t text)
// Use inline version of the siphash code for performance:
#include "siphash.h"
#include "siphash-internals.h"
static const Path_t HOME_PATH = {.root=PATH_HOME}, ROOT_PATH = {.root=PATH_ROOT}, CURDIR_PATH = {.root=PATH_RELATIVE};
static void clean_components(Array_t *components)
{
if (Text$has(text, Pattern("/")))
fail("Path interpolations cannot contain slashes: %k", &text);
else if (Text$has(text, Pattern(";")))
fail("Path interpolations cannot contain semicolons: %k", &text);
else if (Text$equal_values(text, Path(".")) || Text$equal_values(text, Path("..")))
fail("Path interpolation is \"%k\" which is disallowed to prevent security vulnerabilities", &text);
return (Path_t)text;
}
PUREFUNC public Path_t Path$escape_path(Path_t path)
{
if (Text$starts_with(path, Path("~/")) || Text$starts_with(path, Path("/")))
fail("Invalid path component: %k", &path);
return path;
}
public Path_t Path$cleanup(Path_t path)
{
if (!Text$starts_with(path, Path("/")) && !Text$starts_with(path, Path("./"))
&& !Text$starts_with(path, Path("../")) && !Text$starts_with(path, Path("~/")))
path = Text$concat(Text("./"), path);
// Not fully resolved, but at least get rid of some of the cruft like "/./"
// and "/foo/../" and "//"
bool trailing_slash = Text$ends_with(path, Path("/"));
Array_t components = Text$split(path, Pattern("/"));
if (components.length == 0) return Path("/");
Path_t root = *(Path_t*)components.data;
Array$remove_at(&components, I(1), I(1), sizeof(Path_t));
for (int64_t i = 0; i < components.length; ) {
Path_t component = *(Path_t*)(components.data + i*components.stride);
if (component.length == 0 || Text$equal_values(component, Path("."))) { // Skip (//) and (/./)
Array$remove_at(&components, I(i+1), I(1), sizeof(Path_t));
} else if (Text$equal_values(component, Path(".."))) {
if (i == 0) {
if (root.length == 0) { // (/..) -> (/)
Array$remove_at(&components, I(i+1), I(1), sizeof(Path_t));
i += 1;
} else if (Text$equal_values(root, Path("."))) { // (./..) -> (..)
root = Path("..");
Array$remove_at(&components, I(i+1), I(1), sizeof(Path_t));
i += 1;
} else if (Text$equal_values(root, Path("~"))) {
root = Path(""); // Convert $HOME to absolute path:
Array$remove_at(&components, I(i+1), I(1), sizeof(Path_t));
// `i` is pointing to where the `..` lived
const char *home = getenv("HOME");
if (!home) fail("Could not get $HOME directory!");
// Insert all but the last component:
for (const char *p = home + 1; *p; ) {
const char *next_slash = strchr(p, '/');
if (!next_slash) break; // Skip last component
Path_t home_component = Text$format("%.*s", (int)(next_slash - p), p);
Array$insert(&components, &home_component, I(i+1), sizeof(Path_t));
i += 1;
p = next_slash + 1;
}
} else { // (../..) -> (../..)
i += 1;
}
} else if (Text$equal(&component, (Path_t*)(components.data + (i-1)*components.stride), &Text$info)) { // (___/../..) -> (____/../..)
i += 1;
} else { // (___/foo/..) -> (___)
Array$remove_at(&components, I(i), I(2), sizeof(Path_t));
for (int64_t i = 0; i < components->length; ) {
Text_t *component = (Text_t*)(components->data + i*components->stride);
if (component->length == 0 || Text$equal_values(*component, Text("."))) {
Array$remove_at(components, I(i+1), I(1), sizeof(Text_t));
} else if (i > 0 && Text$equal_values(*component, Text(".."))) {
Text_t *prev = (Text_t*)(components->data + (i-1)*components->stride);
if (!Text$equal_values(*prev, Text(".."))) {
Array$remove_at(components, I(i), I(2), sizeof(Text_t));
i -= 1;
} else {
i += 1;
}
} else { // (___/foo/baz) -> (___/foo/baz)
i++;
} else {
i += 1;
}
}
}
Text_t cleaned_up = Text$concat(root, Text("/"), Text$join(Text("/"), components));
if (trailing_slash && !Text$ends_with(cleaned_up, Text("/")))
cleaned_up = Text$concat(cleaned_up, Text("/"));
return cleaned_up;
public Path_t Path$from_str(const char *str)
{
if (!str || str[0] == '\0' || streq(str, "/")) return ROOT_PATH;
else if (streq(str, "~")) return HOME_PATH;
else if (streq(str, ".")) return CURDIR_PATH;
Path_t result = {.components={}};
if (str[0] == '/') {
result.root = PATH_ROOT;
str += 1;
} else if (str[0] == '~' && str[1] == '/') {
result.root = PATH_HOME;
str += 2;
} else if (str[0] == '.' && str[1] == '/') {
result.root = PATH_RELATIVE;
str += 2;
} else {
result.root = PATH_RELATIVE;
}
while (str && *str) {
size_t component_len = strcspn(str, "/");
if (component_len > 0) {
if (component_len == 1 && str[0] == '.') {
// ignore /./
} else if (component_len == 2 && strncmp(str, "..", 2) == 0
&& result.components.length > 1
&& !Text$equal_values(Text(".."), *(Text_t*)(result.components.data + result.components.stride*(result.components.length-1)))) {
// Pop off /foo/baz/.. -> /foo
Array$remove_at(&result.components, I(result.components.length), I(1), sizeof(Text_t));
} else {
Text_t component = Text$from_strn(str, component_len);
Array$insert_value(&result.components, component, I(0), sizeof(Text_t));
}
str += component_len;
}
str += strspn(str, "/");
}
return result;
}
public Path_t Path$from_text(Text_t text)
{
return Path$from_str(Text$as_c_string(text));
}
static INLINE Path_t Path$_expand_home(Path_t path)
{
if (Text$starts_with(path, Path("~/"))) {
Path_t after_tilde = Text$slice(path, I(2), I(-1));
return Text$format("%s%k", getenv("HOME"), &after_tilde);
} else {
return path;
if (path.root == PATH_HOME) {
Path_t pwd = Path$from_str(getenv("HOME"));
Array_t components = Array$concat(path.components, pwd.components, sizeof(Text_t));
clean_components(&components);
path = (Path_t){.root=PATH_ROOT, .components=components};
}
return path;
}
public Path_t Path$_concat(int n, Path_t items[n])
{
Path_t cleaned_up = Path$cleanup(Text$_concat(n, items));
if (cleaned_up.length > PATH_MAX)
fail("Path exceeds the maximum path length: %k", &cleaned_up);
return cleaned_up;
}
public Text_t Path$resolved(Path_t path, Path_t relative_to)
{
path = Path$cleanup(path);
const char *path_str = Text$as_c_string(path);
const char *relative_to_str = Text$as_c_string(relative_to);
const char *resolved_path = resolve_path(path_str, relative_to_str, relative_to_str);
if (resolved_path) {
return (Path_t)(Text$from_str(resolved_path));
} else if (path_str[0] == '/') {
return path;
} else if (path_str[0] == '~' && path_str[1] == '/') {
return (Path_t)Text$format("%s%s", getenv("HOME"), path_str + 1);
} else {
return Text$concat(Path$resolved(relative_to, Path(".")), Path("/"), path);
assert(n > 0);
Path_t result = items[0];
ARRAY_INCREF(result.components);
for (int i = 1; i < n; i++) {
if (items[i].root != PATH_RELATIVE)
fail("Cannot concatenate an absolute or home-based path onto another path: (%s)\n",
Path$as_c_string(items[i]));
Array$insert_all(&result.components, items[i].components, I(0), sizeof(Text_t));
}
clean_components(&result.components);
return result;
}
public Text_t Path$relative(Path_t path, Path_t relative_to)
public Path_t Path$resolved(Path_t path, Path_t relative_to)
{
path = Path$resolved(path, relative_to);
relative_to = Path$resolved(relative_to, Path("."));
if (Text$starts_with(path, Text$concat(relative_to, Text("/"))))
return Text$slice(path, I(relative_to.length + 2), I(-1));
if (path.root == PATH_RELATIVE && !(relative_to.root == PATH_RELATIVE && relative_to.components.length == 0)) {
Path_t result = {.root=relative_to.root};
result.components = relative_to.components;
ARRAY_INCREF(result.components);
Array$insert_all(&result.components, path.components, I(0), sizeof(Text_t));
clean_components(&result.components);
return result;
}
return path;
}
public Path_t Path$relative_to(Path_t path, Path_t relative_to)
{
if (path.root != relative_to.root)
fail("Cannot create a path relative to a different path with a mismatching root: (%k) relative to (%k)",
(Text_t[1]){Path$as_text(&path, false, &Path$info)}, (Text_t[1]){Path$as_text(&relative_to, false, &Path$info)});
Path_t result = {.root=PATH_RELATIVE};
int64_t shared = 0;
for (; shared < path.components.length && shared < relative_to.components.length; shared++) {
Text_t *p = (Text_t*)(path.components.data + shared*path.components.stride);
Text_t *r = (Text_t*)(relative_to.components.data + shared*relative_to.components.stride);
if (!Text$equal_values(*p, *r))
break;
}
for (int64_t i = shared; i < relative_to.components.length; shared++)
Array$insert_value(&result.components, Text(".."), I(1), sizeof(Text_t));
for (int64_t i = shared; i < path.components.length; shared++) {
Text_t *p = (Text_t*)(path.components.data + i*path.components.stride);
Array$insert(&result.components, p, I(0), sizeof(Text_t));
}
//clean_components(&result.components);
return result;
}
public bool Path$exists(Path_t path)
{
path = Path$_expand_home(path);
struct stat sb;
return (stat(Text$as_c_string(path), &sb) == 0);
return (stat(Path$as_c_string(path), &sb) == 0);
}
static INLINE int path_stat(Path_t path, bool follow_symlinks, struct stat *sb)
{
path = Path$_expand_home(path);
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
return follow_symlinks ? stat(path_str, sb) : lstat(path_str, sb);
}
@ -232,7 +242,7 @@ public OptionalMoment_t Path$changed(Path_t path, bool follow_symlinks)
static void _write(Path_t path, Array_t bytes, int mode, int permissions)
{
path = Path$_expand_home(path);
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
int fd = open(path_str, mode, permissions);
if (fd == -1)
fail("Could not write to file: %s\n%s", path_str, strerror(errno));
@ -269,7 +279,7 @@ public void Path$append_bytes(Path_t path, Array_t bytes, int permissions)
public OptionalArray_t Path$read_bytes(Path_t path, OptionalInt_t count)
{
path = Path$_expand_home(path);
int fd = open(Text$as_c_string(path), O_RDONLY);
int fd = open(Path$as_c_string(path), O_RDONLY);
if (fd == -1)
return NONE_ARRAY;
@ -333,7 +343,7 @@ public OptionalText_t Path$read(Path_t path)
public void Path$remove(Path_t path, bool ignore_missing)
{
path = Path$_expand_home(path);
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
struct stat sb;
if (lstat(path_str, &sb) != 0) {
if (!ignore_missing)
@ -354,27 +364,10 @@ public void Path$remove(Path_t path, bool ignore_missing)
public void Path$create_directory(Path_t path, int permissions)
{
path = Path$_expand_home(path);
char *c_path = Text$as_c_string(path);
char *end = c_path + strlen(c_path);
if (*end == '/' && end > c_path) {
*end = '\0';
--end;
}
char *end_of_component = strchrnul(c_path + 1, '/');
for (;;) {
if (end_of_component < end)
*end_of_component = '\0';
int status = mkdir(c_path, (mode_t)permissions);
if (status != 0 && errno != EEXIST)
fail("Could not create directory: %s (%s)", c_path, strerror(errno));
if (end_of_component >= end)
break;
*end_of_component = '/';
end_of_component = strchrnul(end_of_component + 1, '/');
}
const char *c_path = Path$as_c_string(path);
int status = mkdir(c_path, (mode_t)permissions);
if (status != 0 && errno != EEXIST)
fail("Could not create directory: %s (%s)", c_path, strerror(errno));
}
static Array_t _filtered_children(Path_t path, bool include_hidden, mode_t filter)
@ -382,7 +375,7 @@ static Array_t _filtered_children(Path_t path, bool include_hidden, mode_t filte
path = Path$_expand_home(path);
struct dirent *dir;
Array_t children = {};
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
size_t path_len = strlen(path_str);
DIR *d = opendir(path_str);
if (!d)
@ -404,7 +397,7 @@ static Array_t _filtered_children(Path_t path, bool include_hidden, mode_t filte
if (!((sb.st_mode & S_IFMT) & filter))
continue;
Path_t child = Text$format("%s%s", child_str, ((sb.st_mode & S_IFMT) == S_IFDIR) ? "/" : ""); // Trailing slash for dirs
Path_t child = Path$from_str(child_str);
Array$insert(&children, &child, I(0), sizeof(Path_t));
}
closedir(d);
@ -429,7 +422,7 @@ public Array_t Path$subdirectories(Path_t path, bool include_hidden)
public Path_t Path$unique_directory(Path_t path)
{
path = Path$_expand_home(path);
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
if (len >= PATH_MAX) fail("Path is too long: %s", path_str);
char buf[PATH_MAX] = {};
@ -438,13 +431,13 @@ public Path_t Path$unique_directory(Path_t path)
buf[--len] = '\0';
char *created = mkdtemp(buf);
if (!created) fail("Failed to create temporary directory: %s (%s)", path_str, strerror(errno));
return Text$format("%s/", created);
return Path$from_str(created);
}
public Text_t Path$write_unique_bytes(Path_t path, Array_t bytes)
public Path_t Path$write_unique_bytes(Path_t path, Array_t bytes)
{
path = Path$_expand_home(path);
const char *path_str = Text$as_c_string(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
if (len >= PATH_MAX) fail("Path is too long: %s", path_str);
char buf[PATH_MAX] = {};
@ -466,26 +459,39 @@ public Text_t Path$write_unique_bytes(Path_t path, Array_t bytes)
ssize_t written = write(fd, bytes.data, (size_t)bytes.length);
if (written != (ssize_t)bytes.length)
fail("Could not write to file: %s\n%s", buf, strerror(errno));
return Text$format("%s", buf);
return Path$from_str(buf);
}
public Text_t Path$write_unique(Path_t path, Text_t text)
public Path_t Path$write_unique(Path_t path, Text_t text)
{
return Path$write_unique_bytes(path, Text$utf8_bytes(text));
}
public Path_t Path$parent(Path_t path)
{
return Path$cleanup(Text$concat(path, Path("/../")));
if (path.root == PATH_ROOT && path.components.length == 0) {
return path;
} else if (path.components.length > 0 && !Text$equal_values(*(Text_t*)(path.components.data + path.components.stride*(path.components.length-1)),
Text(".."))) {
return (Path_t){.root=path.root, .components=Array$slice(path.components, I(1), I(-2))};
} else {
Path_t result = {.root=path.root, .components=path.components};
ARRAY_INCREF(result.components);
Array$insert_value(&result.components, Text(".."), I(0), sizeof(Text_t));
return result;
}
}
public Text_t Path$base_name(Path_t path)
public PUREFUNC Text_t Path$base_name(Path_t path)
{
path = Path$cleanup(path);
if (Text$ends_with(path, Path("/")))
return Text$replace(path, Pattern("{0+..}/{!/}/{end}"), Text("@2"), Text("@"), false);
if (path.components.length >= 1)
return *(Text_t*)(path.components.data + path.components.stride*(path.components.length-1));
else if (path.root == PATH_HOME)
return Text("~");
else if (path.root == PATH_RELATIVE)
return Text(".");
else
return Text$replace(path, Pattern("{0+..}/{!/}{end}"), Text("@2"), Text("@"), false);
return EMPTY_TEXT;
}
public Text_t Path$extension(Path_t path, bool full)
@ -501,6 +507,41 @@ public Text_t Path$extension(Path_t path, bool full)
return Text("");
}
public Path_t Path$with_component(Path_t path, Text_t component)
{
Path_t result = {
.root=path.root,
.components=path.components,
};
ARRAY_INCREF(result.components);
Array$insert(&result.components, &component, I(0), sizeof(Text_t));
return result;
}
public Path_t Path$with_extension(Path_t path, Text_t extension, bool replace)
{
if (path.components.length == 0)
fail("A path with no components can't have an extension!");
Path_t result = {
.root=path.root,
.components=path.components,
};
ARRAY_INCREF(result.components);
Text_t last = *(Text_t*)(path.components.data + path.components.stride*(path.components.length-1));
Array$remove_at(&result.components, I(-1), I(1), sizeof(Text_t));
if (replace) {
if (Text$starts_with(last, Text(".")))
last = Text$replace(last, Pattern(".{!.}.{..}"), Text(".@1"), Pattern("@"), false);
else
last = Text$replace(last, Pattern("{!.}.{..}"), Text("@1"), Pattern("@"), false);
}
last = Text$concat(last, extension);
Array$insert(&result.components, &last, I(0), sizeof(Text_t));
return result;
}
static void _line_reader_cleanup(FILE **f)
{
if (f && *f) {
@ -536,7 +577,7 @@ public OptionalClosure_t Path$by_line(Path_t path)
{
path = Path$_expand_home(path);
FILE *f = fopen(Text$as_c_string(path), "r");
FILE *f = fopen(Path$as_c_string(path), "r");
if (f == NULL)
return NONE_CLOSURE;
@ -549,7 +590,7 @@ public OptionalClosure_t Path$by_line(Path_t path)
public Array_t Path$glob(Path_t path)
{
glob_t glob_result;
int status = glob(Text$as_c_string(path), GLOB_BRACE | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &glob_result);
int status = glob(Path$as_c_string(path), GLOB_BRACE | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &glob_result);
if (status != 0 && status != GLOB_NOMATCH)
fail("Failed to perform globbing");
@ -559,17 +600,141 @@ public Array_t Path$glob(Path_t path)
if ((len >= 2 && glob_result.gl_pathv[i][len-1] == '.' && glob_result.gl_pathv[i][len-2] == '/')
|| (len >= 2 && glob_result.gl_pathv[i][len-1] == '.' && glob_result.gl_pathv[i][len-2] == '.' && glob_result.gl_pathv[i][len-3] == '/'))
continue;
Array$insert(&glob_files, (Text_t[1]){Text$from_str(glob_result.gl_pathv[i])}, I(0), sizeof(Text_t));
Path_t p = Path$from_str(glob_result.gl_pathv[i]);
Array$insert(&glob_files, &p, I(0), sizeof(Path_t));
}
return glob_files;
}
public PUREFUNC uint64_t Path$hash(const void *obj, const TypeInfo_t *type)
{
(void)type;
Path_t *path = (Path_t*)obj;
siphash sh;
siphashinit(&sh, (uint64_t)path->root);
for (int64_t i = 0; i < path->components.length; i++) {
uint64_t item_hash = Text$hash(path->components.data + i*path->components.stride, &Text$info);
siphashadd64bits(&sh, item_hash);
}
return siphashfinish_last_part(&sh, (uint64_t)path->components.length);
}
public PUREFUNC int32_t Path$compare(const void *va, const void *vb, const TypeInfo_t *type)
{
(void)type;
Path_t *a = (Path_t*)va, *b = (Path_t*)vb;
int diff = ((int)a->root - (int)b->root);
if (diff != 0) return diff;
return Array$compare(&a->components, &b->components, Array$info(&Text$info));
}
public PUREFUNC bool Path$equal(const void *va, const void *vb, const TypeInfo_t *type)
{
(void)type;
Path_t *a = (Path_t*)va, *b = (Path_t*)vb;
if (a->root != b->root) return false;
return Array$equal(&a->components, &b->components, Array$info(&Text$info));
}
public PUREFUNC bool Path$equal_values(Path_t a, Path_t b)
{
if (a.root != b.root) return false;
return Array$equal(&a.components, &b.components, Array$info(&Text$info));
}
public const char *Path$as_c_string(Path_t path)
{
if (path.components.length == 0) {
if (path.root == PATH_ROOT) return "/";
else if (path.root == PATH_RELATIVE) return ".";
else if (path.root == PATH_HOME) return "~";
}
size_t len = 0, capacity = 16;
char *buf = GC_MALLOC_ATOMIC(capacity);
if (path.root == PATH_ROOT) {
buf[len++] = '/';
} else if (path.root == PATH_HOME) {
buf[len++] = '~';
buf[len++] = '/';
} else if (path.root == PATH_RELATIVE) {
if (!Text$equal_values(*(Text_t*)path.components.data, Text(".."))) {
buf[len++] = '.';
buf[len++] = '/';
}
}
for (int64_t i = 0; i < path.components.length; i++) {
Text_t *comp = (Text_t*)(path.components.data + i*path.components.stride);
const char *comp_str = Text$as_c_string(*comp);
size_t comp_len = strlen(comp_str);
if (len + comp_len + 1 > capacity) {
buf = GC_REALLOC(buf, (capacity += MIN(comp_len + 2, 16)));
}
memcpy(&buf[len], comp_str, comp_len);
len += comp_len;
if (i + 1 < path.components.length)
buf[len++] = '/';
}
buf[len++] = '\0';
return buf;
}
public Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type)
{
(void)type;
if (!obj) return Text("Path");
Path_t *path = (Path_t*)obj;
Text_t text = Text$join(Text("/"), path->components);
if (path->root == PATH_HOME)
text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
else if (path->root == PATH_ROOT)
text = Text$concat(Text("/"), text);
else if (path->root == PATH_RELATIVE && path->components.length > 0 && !Text$equal_values(*(Text_t*)(path->components.data), Text("..")))
text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text);
if (color)
text = Texts(Text("\033[32;1m"), text, Text("\033[m"));
return text;
}
public CONSTFUNC bool Path$is_none(const void *obj, const TypeInfo_t *type)
{
(void)type;
return ((Path_t*)obj)->root == PATH_NONE;
}
public void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type)
{
(void)type;
Path_t *path = (Path_t*)obj;
fputc((int)path->root, out);
Array$serialize(&path->components, out, pointers, Array$info(&Text$info));
}
public void Path$deserialize(FILE *in, void *obj, Array_t *pointers, const TypeInfo_t *type)
{
(void)type;
Path_t path = {};
path.root = fgetc(in);
Array$deserialize(in, &path.components, pointers, Array$info(&Text$info));
*(Path_t*)obj = path;
}
public const TypeInfo_t Path$info = {
.size=sizeof(Path_t),
.align=__alignof__(Path_t),
.tag=TextInfo,
.TextInfo={.lang="Path"},
.metamethods=Text$metamethods,
.tag=OpaqueInfo,
.metamethods={
.as_text=Path$as_text,
.hash=Path$hash,
.compare=Path$compare,
.equal=Path$equal,
.is_none=Path$is_none,
.serialize=Path$serialize,
.deserialize=Path$deserialize,
}
};
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -9,18 +9,15 @@
#include "datatypes.h"
#include "optionals.h"
#define Path_t Text_t
#define OptionalPath_t Text_t
#define Path(text) ((Path_t)Text(text))
#define Paths(...) Path$_concat(sizeof((Path_t[]){__VA_ARGS__})/sizeof(Path_t), (Path_t[]){__VA_ARGS__})
Path_t Path$cleanup(Path_t path);
Path_t Path$from_str(const char *str);
Path_t Path$from_text(Text_t text);
const char *Path$as_c_string(Path_t path);
#define Path(str) Path$from_str(str)
Path_t Path$_concat(int n, Path_t items[n]);
#define Path$concat(a, b) Paths(a, Path("/"), b)
PUREFUNC Path_t Path$escape_text(Text_t text);
PUREFUNC Path_t Path$escape_path(Text_t path);
#define Path$concat(...) Path$_concat((int)sizeof((Path_t[]){__VA_ARGS__})/sizeof(Path_t), ((Path_t[]){__VA_ARGS__}))
Path_t Path$resolved(Path_t path, Path_t relative_to);
Path_t Path$relative(Path_t path, Path_t relative_to);
Path_t Path$relative_to(Path_t path, Path_t relative_to);
bool Path$exists(Path_t path);
bool Path$is_file(Path_t path, bool follow_symlinks);
bool Path$is_directory(Path_t path, bool follow_symlinks);
@ -42,17 +39,24 @@ Array_t Path$children(Path_t path, bool include_hidden);
Array_t Path$files(Path_t path, bool include_hidden);
Array_t Path$subdirectories(Path_t path, bool include_hidden);
Path_t Path$unique_directory(Path_t path);
Text_t Path$write_unique(Path_t path, Text_t text);
Text_t Path$write_unique_bytes(Path_t path, Array_t bytes);
Path_t Path$write_unique(Path_t path, Text_t text);
Path_t Path$write_unique_bytes(Path_t path, Array_t bytes);
Path_t Path$parent(Path_t path);
Text_t Path$base_name(Path_t path);
Text_t Path$extension(Path_t path, bool full);
Path_t Path$with_component(Path_t path, Text_t component);
Path_t Path$with_extension(Path_t path, Text_t extension, bool replace);
Closure_t Path$by_line(Path_t path);
Array_t Path$glob(Path_t path);
#define Path$hash Text$hash
#define Path$compare Text$compare
#define Path$equal Text$equal
uint64_t Path$hash(const void *obj, const TypeInfo_t*);
int32_t Path$compare(const void *a, const void *b, const TypeInfo_t *type);
bool Path$equal(const void *a, const void *b, const TypeInfo_t *type);
bool Path$equal_values(Path_t a, Path_t b);
Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type);
bool Path$is_none(const void *obj, const TypeInfo_t *type);
void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type);
void Path$deserialize(FILE *in, void *obj, Array_t *pointers, const TypeInfo_t *type);
extern const TypeInfo_t Path$info;

View File

@ -118,20 +118,7 @@ static bool parse_single_arg(const TypeInfo_t *info, char *arg, void *dest)
*(OptionalNum32_t*)dest = parsed;
return !isnan(parsed);
} else if (info == &Path$info) {
Path_t path = Text$from_str(arg);
if (Text$equal_values(path, Path("~"))) {
path = Path("~/");
} else if (Text$equal_values(path, Path("."))) {
path = Path("./");
} else if (Text$equal_values(path, Path(".."))) {
path = Path("../");
} else if (!Text$starts_with(path, Text("./"))
&& !Text$starts_with(path, Text("../"))
&& !Text$starts_with(path, Text("/"))
&& !Text$starts_with(path, Text("~/"))) {
path = Text$concat(Text("./"), path);
}
*(OptionalText_t*)dest = path;
*(OptionalPath_t*)dest = Path$from_str(arg);
return true;
} else if (info->tag == TextInfo) {
*(OptionalText_t*)dest = Text$from_str(arg);

View File

@ -6,14 +6,14 @@ func main():
= yes
>> (~/Downloads/file(1).txt)
= (~/Downloads/file(1).txt)
= ~/Downloads/file(1).txt
>> (/half\)paren)
= (/half\)paren)
= /half)paren
>> filename := "example.txt"
>> (~/$filename)
= (~/example.txt)
>> (~):child(filename)
= ~/example.txt
>> tmpdir := (/tmp/tomo-test-path-XXXXXX):unique_directory()
>> (/tmp):subdirectories():has(tmpdir)
@ -55,7 +55,7 @@ func main():
>> p:base_name()
= "qux.tar.gz"
>> p:parent()
= (/foo/baz.x/)
= /foo/baz.x
>> p:extension()
= "tar.gz"
>> p:extension(full=no)
@ -69,69 +69,62 @@ func main():
= "baz.qux"
>> (/):parent()
= (/)
= /
>> (~/x/.):parent()
= (~/)
= ~
>> (~/x):parent()
= (~/)
>> (./):parent()
= (../)
>> (../):parent()
= (../../)
= ~
>> (.):parent()
= ..
>> (..):parent()
= ../..
>> (../foo):parent()
= (../)
>> (./foo.txt):ends_with(".txt")
= yes
>> (./foo.txt):matches($|{..}/foo{..}|)
= [".", ".txt"] : [Text]?
>> (./foo.txt):replace($/.txt/, ".md")
= (./foo.md)
= ..
# Concatenation tests:
!! Basic relative path concatenation:
>> (/foo) ++ (./baz)
= (/foo/baz)
= /foo/baz
!! Concatenation with a current directory (`.`):
>> (/foo/bar) ++ (./.)
= (/foo/bar)
= /foo/bar
!! Trailing slash in the first path:
>> (/foo/) ++ (./baz)
= (/foo/baz)
= /foo/baz
!! Trailing slash in the second path:
>> (/foo/bar) ++ (./baz/)
= (/foo/bar/baz/)
= /foo/bar/baz
!! Removing redundant current directory (`.`):
>> (/foo/bar) ++ (./baz/./qux)
= (/foo/bar/baz/qux)
= /foo/bar/baz/qux
!! Removing redundant parent directory (`..`):
>> (/foo/bar) ++ (./baz/qux/../quux)
= (/foo/bar/baz/quux)
= /foo/bar/baz/quux
!! Collapsing `..` to navigate up:
>> (/foo/bar/baz) ++ (../qux)
= (/foo/bar/qux)
= /foo/bar/qux
!! Current directory and parent directory mixed:
>> (/foo/bar) ++ (././../baz)
= (/foo/baz)
= /foo/baz
!! Path begins with a `.`:
>> (/foo) ++ (./baz/../qux)
= (/foo/qux)
= /foo/qux
!! Multiple slashes:
>> (/foo) ++ (./baz//qux)
= (/foo/baz/qux)
= /foo/baz/qux
!! Complex path with multiple `.` and `..`:
>> (/foo/bar/baz) ++ (./.././qux/./../quux)
= (/foo/bar/quux)
= /foo/bar/quux
!! Globbing:
>> (./*.tm):glob()

267
tomo.c
View File

@ -2,7 +2,6 @@
#include <ctype.h>
#include <gc.h>
#include <gc/cord.h>
#include <glob.h>
#include <libgen.h>
#include <printf.h>
#include <stdio.h>
@ -30,15 +29,25 @@
#define run_cmd(...) ({ const char *_cmd = heap_strf(__VA_ARGS__); if (verbose) puts(_cmd); popen(_cmd, "w"); })
#define array_str(arr) Text$as_c_string(Text$join(Text(" "), arr))
static const char *paths_str(Array_t paths) {
Text_t result = EMPTY_TEXT;
for (int64_t i = 0; i < paths.length; i++) {
if (i > 0) result = Texts(result, Text(" "));
result = Texts(result, Path$as_text((Path_t*)(paths.data + i*paths.stride), false, &Path$info));
}
return Text$as_c_string(result);
}
static OptionalArray_t files = NONE_ARRAY,
args = NONE_ARRAY;
args = NONE_ARRAY,
uninstall = NONE_ARRAY,
libraries = NONE_ARRAY;
static OptionalBool_t verbose = false,
stop_at_transpile = false,
stop_at_obj_compilation = false,
stop_at_exe_compilation = false,
should_install = false,
library_mode = false,
uninstall = false;
run_repl = false;
static OptionalText_t
show_codegen = NONE_TEXT,
@ -50,17 +59,17 @@ static OptionalText_t
ldlibs = Text("-lgc -lgmp -lm -ltomo"),
ldflags = Text("-Wl,-rpath='$ORIGIN',-rpath=$HOME/.local/share/tomo/lib -L. -L$HOME/.local/share/tomo/lib"),
optimization = Text("2"),
cc = Text("cc");
cc = Text("gcc");
static void transpile_header(env_t *base_env, Text_t filename, bool force_retranspile);
static void transpile_code(env_t *base_env, Text_t filename, bool force_retranspile);
static void compile_object_file(Text_t filename, bool force_recompile);
static Text_t compile_executable(env_t *base_env, Text_t filename, Array_t object_files, Array_t extra_ldlibs);
static void build_file_dependency_graph(Text_t filename, Table_t *to_compile, Table_t *to_link);
static void transpile_header(env_t *base_env, Path_t path, bool force_retranspile);
static void transpile_code(env_t *base_env, Path_t path, bool force_retranspile);
static void compile_object_file(Path_t path, bool force_recompile);
static Path_t compile_executable(env_t *base_env, Path_t path, Array_t object_files, Array_t extra_ldlibs);
static void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_link);
static Text_t escape_lib_name(Text_t lib_name);
static void build_library(Text_t lib_dir_name);
static void compile_files(env_t *env, Array_t files, bool only_compile_arguments, Array_t *object_files, Array_t *ldlibs);
static bool is_stale(Path_t filename, Path_t relative_to);
static bool is_stale(Path_t path, Path_t relative_to);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-protector"
@ -99,7 +108,7 @@ int main(int argc, char *argv[])
Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage);
tomo_parse_args(
argc, argv, usage, help,
{"files", true, Array$info(&Text$info), &files},
{"files", true, Array$info(&Path$info), &files},
{"args", true, Array$info(&Text$info), &args},
{"verbose", false, &Bool$info, &verbose},
{"v", false, &Bool$info, &verbose},
@ -109,67 +118,69 @@ int main(int argc, char *argv[])
{"c", false, &Bool$info, &stop_at_obj_compilation},
{"compile-exe", false, &Bool$info, &stop_at_exe_compilation},
{"e", false, &Bool$info, &stop_at_exe_compilation},
{"uninstall", false, &Bool$info, &uninstall},
{"u", false, &Bool$info, &uninstall},
{"library", false, &Bool$info, &library_mode},
{"L", false, &Bool$info, &library_mode},
{"uninstall", false, Array$info(&Text$info), &uninstall},
{"u", false, Array$info(&Text$info), &uninstall},
{"library", false, Array$info(&Path$info), &libraries},
{"L", false, Array$info(&Path$info), &libraries},
{"show-codegen", false, &Text$info, &show_codegen},
{"C", false, &Text$info, &show_codegen},
{"repl", false, &Bool$info, &run_repl},
{"R", false, &Bool$info, &run_repl},
{"install", false, &Bool$info, &should_install},
{"I", false, &Bool$info, &should_install},
{"c-compiler", false, &Text$info, &cc},
{"optimization", false, &Text$info, &optimization},
{"O", false, &Text$info, &optimization},
);
if (show_codegen.length > 0 && Text$equal_values(show_codegen, Text("pretty")))
show_codegen = Text("sed '/^#line/d;/^$/d' | indent -o /dev/stdout | bat -l c -P");
if (uninstall) {
for (int64_t i = 0; i < files.length; i++) {
Text_t arg = *(Text_t*)(files.data + i*files.stride);
system(heap_strf("rm -rvf ~/.local/share/tomo/installed/%k ~/.local/share/tomo/lib/lib%k.so",
&arg, &arg));
}
return 0;
} else if (library_mode) {
char *cwd = get_current_dir_name();
if (files.length == 0)
files = (Array_t){.length=1, .stride=sizeof(Text_t), .data=(Text_t[]){Text(".")}};
for (int64_t i = 0; i < uninstall.length; i++) {
Text_t *u = (Text_t*)(uninstall.data + i*uninstall.stride);
system(heap_strf("rm -rvf ~/.local/share/tomo/installed/%k ~/.local/share/tomo/lib/lib%k.so", u, u));
printf("Uninstalled %k\n", u);
}
for (int64_t i = 0; i < files.length; i++) {
Text_t arg = *(Text_t*)(files.data + i*files.stride);
if (chdir(Text$as_c_string(arg)) != 0)
errx(1, "Could not enter directory: %k", &arg);
char *libdir = get_current_dir_name();
char *libdirname = basename(libdir);
build_library(Text$from_str(libdirname));
free(libdir);
chdir(cwd);
}
for (int64_t i = 0; i < libraries.length; i++) {
Path_t *lib = (Path_t*)(libraries.data + i*libraries.stride);
const char *lib_str = Path$as_c_string(*lib);
char *cwd = get_current_dir_name();
if (chdir(lib_str) != 0)
errx(1, "Could not enter directory: %s", lib_str);
char *libdir = get_current_dir_name();
char *libdirname = basename(libdir);
build_library(Text$from_str(libdirname));
free(libdir);
chdir(cwd);
free(cwd);
return 0;
} else if (files.length == 0) {
if (show_codegen.length >= 0)
errx(1, "You specified to show codegen with the tool `%k` but didn't give any files", &show_codegen);
}
// TODO: REPL
if (run_repl) {
repl();
return 0;
}
if (files.length <= 0 && (uninstall.length > 0 || libraries.length > 0)) {
return 0;
}
// Run file directly:
if (!stop_at_transpile && !stop_at_obj_compilation && !stop_at_exe_compilation) {
if (files.length < 1)
errx(1, "No file specified!");
else if (files.length != 1)
errx(1, "Too many files specified!");
Text_t filename = *(Text_t*)files.data;
Path_t path = *(Path_t*)files.data;
env_t *env = new_compilation_unit(NULL);
Array_t object_files = {},
extra_ldlibs = {};
compile_files(env, files, false, &object_files, &extra_ldlibs);
Text_t exe_name = compile_executable(env, filename, object_files, extra_ldlibs);
Path_t exe_name = compile_executable(env, path, object_files, extra_ldlibs);
char *prog_args[1 + args.length + 1];
prog_args[0] = Text$as_c_string(exe_name);
prog_args[0] = (char*)Path$as_c_string(exe_name);
for (int64_t i = 0; i < args.length; i++)
prog_args[i + 1] = Text$as_c_string(*(Text_t*)(args.data + i*args.stride));
prog_args[1 + args.length] = NULL;
@ -184,10 +195,10 @@ int main(int argc, char *argv[])
return 0;
for (int64_t i = 0; i < files.length; i++) {
Text_t filename = *(Text_t*)(files.data + i*files.stride);
Text_t bin_name = compile_executable(env, filename, object_files, extra_ldlibs);
Path_t path = *(Path_t*)(files.data + i*files.stride);
Path_t bin_name = compile_executable(env, path, object_files, extra_ldlibs);
if (should_install)
system(heap_strf("cp -v '%k' ~/.local/bin/", &bin_name));
system(heap_strf("cp -v '%s' ~/.local/bin/", Path$as_c_string(bin_name)));
}
return 0;
}
@ -248,15 +259,15 @@ static void _make_typedefs_for_library(libheader_info_t *info, ast_t *ast)
}
}
static void _compile_file_header_for_library(env_t *env, Text_t filename, Table_t *visited_files, Table_t *used_imports, FILE *output)
static void _compile_file_header_for_library(env_t *env, Path_t path, Table_t *visited_files, Table_t *used_imports, FILE *output)
{
if (Table$get(*visited_files, &filename, Table$info(&Text$info, &Bool$info)))
if (Table$get(*visited_files, &path, Table$info(&Path$info, &Bool$info)))
return;
Table$set(visited_files, &filename, ((Bool_t[1]){1}), Table$info(&Text$info, &Bool$info));
Table$set(visited_files, &path, ((Bool_t[1]){1}), Table$info(&Path$info, &Bool$info));
ast_t *file_ast = parse_file(Text$as_c_string(filename), NULL);
if (!file_ast) errx(1, "Could not parse file %k", &filename);
ast_t *file_ast = parse_file(Path$as_c_string(path), NULL);
if (!file_ast) errx(1, "Could not parse file %s", Path$as_c_string(path));
env_t *module_env = load_module_env(env, file_ast);
libheader_info_t info = {
@ -274,7 +285,7 @@ static void _compile_file_header_for_library(env_t *env, Text_t filename, Table_
auto use = Match(ast, Use);
if (use->what == USE_LOCAL) {
Path_t resolved = Path$resolved(Text$from_str(use->path), Path("./"));
Path_t resolved = Path$resolved(Path$from_str(use->path), Path("./"));
_compile_file_header_for_library(env, resolved, visited_files, used_imports, output);
}
}
@ -289,19 +300,11 @@ static void _compile_file_header_for_library(env_t *env, Text_t filename, Table_
void build_library(Text_t lib_dir_name)
{
glob_t tm_files;
char *library_directory = get_current_dir_name();
if (glob("[!._0-9]*.tm", 0, NULL, &tm_files) != 0)
errx(1, "Couldn't get .tm files in directory: %s", library_directory);
Array_t glob_files = {};
for (size_t i = 0; i < tm_files.gl_pathc; i++)
Array$insert(&glob_files, (Text_t[1]){Text$from_str(tm_files.gl_pathv[i])}, I(0), sizeof(Text_t));
Array_t tm_files = Path$glob(Path("./[!._0-9]*.tm"));
env_t *env = new_compilation_unit(NULL);
Array_t object_files = {},
extra_ldlibs = {};
compile_files(env, glob_files, false, &object_files, &extra_ldlibs);
compile_files(env, tm_files, false, &object_files, &extra_ldlibs);
// Library name replaces all stretchs of non-alphanumeric chars with an underscore
// So e.g. https://github.com/foo/baz --> https_github_com_foo_baz
@ -313,9 +316,9 @@ void build_library(Text_t lib_dir_name)
fputs("#include <tomo/tomo.h>\n", header);
Table_t visited_files = {};
Table_t used_imports = {};
for (size_t i = 0; i < tm_files.gl_pathc; i++) {
const char *filename = tm_files.gl_pathv[i];
Path_t resolved = Path$resolved(Text$from_str(filename), Path("."));
for (int64_t i = 0; i < tm_files.length; i++) {
Path_t f = *(Path_t*)(tm_files.data + i*tm_files.stride);
Path_t resolved = Path$resolved(f, Path("."));
_compile_file_header_for_library(env, resolved, &visited_files, &used_imports, header);
}
if (fclose(header) == -1)
@ -324,20 +327,18 @@ void build_library(Text_t lib_dir_name)
// Build up a list of symbol renamings:
unlink("symbol_renames.txt");
FILE *prog;
for (size_t i = 0; i < tm_files.gl_pathc; i++) {
const char *filename = tm_files.gl_pathv[i];
for (int64_t i = 0; i < tm_files.length; i++) {
Path_t f = *(Path_t*)(tm_files.data + i*tm_files.stride);
prog = run_cmd("nm -Ug -fjust-symbols '%s.o' | sed 's/_\\$\\(.*\\)/\\0 _$%s$\\1/' >>symbol_renames.txt",
filename, CORD_to_const_char_star(env->libname));
Path$as_c_string(f), CORD_to_const_char_star(env->libname));
int status = pclose(prog);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
errx(WEXITSTATUS(status), "Failed to create symbol rename table with `nm` and `sed`");
}
globfree(&tm_files);
prog = run_cmd("%k -O%k %k %k %k %s -Wl,-soname='lib%k.so' -shared %s -o 'lib%k.so'",
&cc, &optimization, &cflags, &ldflags, &ldlibs, array_str(extra_ldlibs), &lib_dir_name,
array_str(object_files), &lib_dir_name);
paths_str(object_files), &lib_dir_name);
if (!prog)
errx(1, "Failed to run C compiler: %k", &cc);
int status = pclose(prog);
@ -363,6 +364,7 @@ void build_library(Text_t lib_dir_name)
unlink("symbol_renames.txt");
if (should_install) {
char *library_directory = get_current_dir_name();
const char *dest = heap_strf("%s/.local/share/tomo/installed/%k", getenv("HOME"), &lib_dir_name);
if (!streq(library_directory, dest)) {
system(heap_strf("rm -rvf '%s'", dest));
@ -372,9 +374,8 @@ void build_library(Text_t lib_dir_name)
system("mkdir -p ~/.local/share/tomo/lib/");
system(heap_strf("ln -fv -s ../installed/'%k'/lib'%k'.so ~/.local/share/tomo/lib/lib'%k'.so",
&lib_dir_name, &lib_dir_name, &lib_dir_name));
free(library_directory);
}
free(library_directory);
}
void compile_files(env_t *env, Array_t to_compile, bool only_compile_arguments, Array_t *object_files, Array_t *extra_ldlibs)
@ -385,11 +386,12 @@ void compile_files(env_t *env, Array_t to_compile, bool only_compile_arguments,
Table_t dependency_files = {};
for (int64_t i = 0; i < to_compile.length; i++) {
Path_t filename = *(Path_t*)(to_compile.data + i*to_compile.stride);
if (!Text$ends_with(filename, Text(".tm")))
errx(1, "Not a valid .tm file: \x1b[31;1m%k\x1b[m", &filename);
Text_t extension = Path$extension(filename, true);
if (!Text$equal_values(extension, Text("tm")))
errx(1, "Not a valid .tm file: \x1b[31;1m%s\x1b[m", Path$as_c_string(filename));
Path_t resolved = Path$resolved(filename, Path("./"));
if (!Path$is_file(resolved, true))
errx(1, "Couldn't find file: %k", &resolved);
errx(1, "Couldn't find file: %s", Path$as_c_string(resolved));
Table$set(&argument_files, &resolved, &filename, path_table_info);
build_file_dependency_graph(resolved, &dependency_files, &to_link);
}
@ -438,10 +440,9 @@ void compile_files(env_t *env, Array_t to_compile, bool only_compile_arguments,
if (object_files) {
for (int64_t i = 0; i < dependency_files.entries.length; i++) {
Path_t filename = Text$concat(
*(Path_t*)(dependency_files.entries.data + i*dependency_files.entries.stride),
Text(".o"));
Array$insert(object_files, &filename, I(0), sizeof(Path_t));
Path_t path = *(Path_t*)(dependency_files.entries.data + i*dependency_files.entries.stride);
path = Path$with_extension(path, Text(".o"), false);
Array$insert(object_files, &path, I(0), sizeof(Path_t));
}
}
if (extra_ldlibs) {
@ -452,19 +453,19 @@ void compile_files(env_t *env, Array_t to_compile, bool only_compile_arguments,
}
}
void build_file_dependency_graph(Text_t filename, Table_t *to_compile, Table_t *to_link)
void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_link)
{
if (Table$get(*to_compile, &filename, Table$info(&Text$info, &Bool$info)))
if (Table$get(*to_compile, &path, Table$info(&Path$info, &Bool$info)))
return;
bool stale = is_stale(Paths(filename, Path(".o")), filename);
Table$set(to_compile, &filename, &stale, Table$info(&Text$info, &Bool$info));
bool stale = is_stale(Path$with_extension(path, Text(".o"), false), path);
Table$set(to_compile, &path, &stale, Table$info(&Path$info, &Bool$info));
assert(Text$ends_with(filename, Text(".tm")));
assert(Text$equal_values(Path$extension(path, true), Text("tm")));
ast_t *ast = parse_file(Text$as_c_string(filename), NULL);
ast_t *ast = parse_file(Path$as_c_string(path), NULL);
if (!ast)
errx(1, "Could not parse file %k", &filename);
errx(1, "Could not parse file %s", Path$as_c_string(path));
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
ast_t *stmt_ast = stmt->ast;
@ -476,10 +477,10 @@ void build_file_dependency_graph(Text_t filename, Table_t *to_compile, Table_t *
switch (use->what) {
case USE_LOCAL: {
Text_t resolved = Path$resolved(Text$from_str(use->path), filename);
if (!stale && is_stale(Texts(filename, Text(".o")), resolved)) {
Path_t resolved = Path$resolved(Path$from_str(use->path), Path$parent(path));
if (!stale && is_stale(Path$with_extension(path, Text(".o"), false), resolved)) {
stale = true;
Table$set(to_compile, &filename, &stale, Table$info(&Text$info, &Bool$info));
Table$set(to_compile, &path, &stale, Table$info(&Path$info, &Bool$info));
}
if (Table$get(*to_compile, &resolved, Table$info(&Path$info, &Path$info)))
continue;
@ -506,60 +507,60 @@ void build_file_dependency_graph(Text_t filename, Table_t *to_compile, Table_t *
}
}
bool is_stale(Path_t filename, Path_t relative_to)
bool is_stale(Path_t path, Path_t relative_to)
{
struct stat target_stat;
if (stat(Text$as_c_string(filename), &target_stat) != 0)
if (stat(Path$as_c_string(path), &target_stat) != 0)
return true;
struct stat relative_to_stat;
if (stat(Text$as_c_string(relative_to), &relative_to_stat) != 0)
errx(1, "File doesn't exist: %k", &relative_to);
if (stat(Path$as_c_string(relative_to), &relative_to_stat) != 0)
errx(1, "File doesn't exist: %s", Path$as_c_string(relative_to));
return target_stat.st_mtime < relative_to_stat.st_mtime;
}
void transpile_header(env_t *base_env, Text_t filename, bool force_retranspile)
void transpile_header(env_t *base_env, Path_t path, bool force_retranspile)
{
Text_t h_filename = Text$concat(filename, Text(".h"));
if (!force_retranspile && !is_stale(h_filename, filename))
Path_t h_filename = Path$with_extension(path, Text(".h"), false);
if (!force_retranspile && !is_stale(h_filename, path))
return;
ast_t *ast = parse_file(Text$as_c_string(filename), NULL);
ast_t *ast = parse_file(Path$as_c_string(path), NULL);
if (!ast)
errx(1, "Could not parse file %k", &filename);
errx(1, "Could not parse file %s", Path$as_c_string(path));
env_t *module_env = load_module_env(base_env, ast);
CORD h_code = compile_file_header(module_env, ast);
FILE *header = fopen(Text$as_c_string(h_filename), "w");
FILE *header = fopen(Path$as_c_string(h_filename), "w");
CORD_put(h_code, header);
if (fclose(header) == -1)
errx(1, "Failed to write header file: %k", &h_filename);
errx(1, "Failed to write header file: %s", Path$as_c_string(h_filename));
if (verbose)
printf("\x1b[2mTranspiled to %k\x1b[m\n", &h_filename);
printf("\x1b[2mTranspiled to %s\x1b[m\n", Path$as_c_string(h_filename));
if (show_codegen.length > 0)
system(heap_strf("<%k %k", &h_filename, &show_codegen));
system(heap_strf("<%s %k", Path$as_c_string(h_filename), &show_codegen));
}
void transpile_code(env_t *base_env, Text_t filename, bool force_retranspile)
void transpile_code(env_t *base_env, Path_t path, bool force_retranspile)
{
Text_t c_filename = Text$concat(filename, Text(".c"));
if (!force_retranspile && !is_stale(c_filename, filename))
Path_t c_filename = Path$with_extension(path, Text(".c"), false);
if (!force_retranspile && !is_stale(c_filename, path))
return;
ast_t *ast = parse_file(Text$as_c_string(filename), NULL);
ast_t *ast = parse_file(Path$as_c_string(path), NULL);
if (!ast)
errx(1, "Could not parse file %k", &filename);
errx(1, "Could not parse file %s", Path$as_c_string(path));
env_t *module_env = load_module_env(base_env, ast);
CORD c_code = compile_file(module_env, ast);
FILE *c_file = fopen(Text$as_c_string(c_filename), "w");
FILE *c_file = fopen(Path$as_c_string(c_filename), "w");
if (!c_file)
errx(1, "Failed to write C file: %k", &c_filename);
errx(1, "Failed to write C file: %s", Path$as_c_string(c_filename));
CORD_put(c_code, c_file);
@ -581,28 +582,28 @@ void transpile_code(env_t *base_env, Text_t filename, bool force_retranspile)
}
if (fclose(c_file) == -1)
errx(1, "Failed to output C code to %k", &c_filename);
errx(1, "Failed to output C code to %s", Path$as_c_string(c_filename));
if (verbose)
printf("\x1b[2mTranspiled to %k\x1b[m\n", &c_filename);
printf("\x1b[2mTranspiled to %s\x1b[m\n", Path$as_c_string(c_filename));
if (show_codegen.length > 0)
system(heap_strf("<%k %k", &c_filename, &show_codegen));
system(heap_strf("<%s %k", Path$as_c_string(c_filename), &show_codegen));
}
void compile_object_file(Text_t filename, bool force_recompile)
void compile_object_file(Path_t path, bool force_recompile)
{
Text_t obj_file = Text$concat(filename, Text(".o"));
Text_t c_file = Text$concat(filename, Text(".c"));
Text_t h_file = Text$concat(filename, Text(".h"));
if (!force_recompile && !is_stale(obj_file, filename)
Path_t obj_file = Path$with_extension(path, Text(".o"), false);
Path_t c_file = Path$with_extension(path, Text(".c"), false);
Path_t h_file = Path$with_extension(path, Text(".h"), false);
if (!force_recompile && !is_stale(obj_file, path)
&& !is_stale(obj_file, c_file)
&& !is_stale(obj_file, h_file)) {
return;
}
FILE *prog = run_cmd("%k %k -O%k -c %k -o %k",
&cc, &cflags, &optimization, &c_file, &obj_file);
FILE *prog = run_cmd("%k %k -O%k -c %s -o %s",
&cc, &cflags, &optimization, Path$as_c_string(c_file), Path$as_c_string(obj_file));
if (!prog)
errx(1, "Failed to run C compiler: %k", &cc);
int status = pclose(prog);
@ -610,23 +611,23 @@ void compile_object_file(Text_t filename, bool force_recompile)
exit(EXIT_FAILURE);
if (verbose)
printf("\x1b[2mCompiled to %k\x1b[m\n", &obj_file);
printf("\x1b[2mCompiled to %s\x1b[m\n", Path$as_c_string(obj_file));
}
Text_t compile_executable(env_t *base_env, Text_t filename, Array_t object_files, Array_t extra_ldlibs)
Path_t compile_executable(env_t *base_env, Path_t path, Array_t object_files, Array_t extra_ldlibs)
{
ast_t *ast = parse_file(Text$as_c_string(filename), NULL);
ast_t *ast = parse_file(Path$as_c_string(path), NULL);
if (!ast)
errx(1, "Could not parse file %k", &filename);
errx(1, "Could not parse file %s", Path$as_c_string(path));
env_t *env = load_module_env(base_env, ast);
binding_t *main_binding = get_binding(env, "main");
if (!main_binding || main_binding->type->tag != FunctionType)
errx(1, "No main() function has been defined for %k, so it can't be run!", &filename);
errx(1, "No main() function has been defined for %s, so it can't be run!", Path$as_c_string(path));
Text_t bin_name = Text$trim(filename, Text(".tm"), false, true);
FILE *runner = run_cmd("%k %k -O%k %k %k %s %s -x c - -o %k",
Path_t bin_name = Path$with_extension(path, Text(""), true);
FILE *runner = run_cmd("%k %k -O%k %k %k %s %s -x c - -o %s",
&cc, &cflags, &optimization, &ldflags, &ldlibs,
array_str(extra_ldlibs), array_str(object_files), &bin_name);
array_str(extra_ldlibs), paths_str(object_files), Path$as_c_string(bin_name));
CORD program = CORD_all(
"extern int ", main_binding->code, "$parse_and_run(int argc, char *argv[]);\n"
"int main(int argc, char *argv[]) {\n"
@ -634,6 +635,10 @@ Text_t compile_executable(env_t *base_env, Text_t filename, Array_t object_files
"}\n"
);
FILE *xxx = fopen("runner.c", "w");
CORD_put(program, xxx);
fclose(xxx);
if (show_codegen.length > 0) {
FILE *out = run_cmd("%k", &show_codegen);
CORD_put(program, out);
@ -646,7 +651,7 @@ Text_t compile_executable(env_t *base_env, Text_t filename, Array_t object_files
exit(EXIT_FAILURE);
if (verbose)
printf("\x1b[2mCompiled executable: %k\x1b[m\n", &bin_name);
printf("\x1b[2mCompiled executable: %s\x1b[m\n", Path$as_c_string(bin_name));
return bin_name;
}

View File

@ -638,6 +638,7 @@ type_t *get_type(env_t *env, ast_t *ast)
return Match(t, OptionalType)->type;
}
case TextLiteral: return TEXT_TYPE;
case Path: return PATH_TYPE;
case TextJoin: {
const char *lang = Match(ast, TextJoin)->lang;
if (lang) {
@ -1050,7 +1051,7 @@ type_t *get_type(env_t *env, ast_t *ast)
return lhs_t;
break;
}
case BINOP_PLUS: case BINOP_MINUS: case BINOP_AND: case BINOP_OR: case BINOP_XOR: {
case BINOP_PLUS: case BINOP_MINUS: case BINOP_AND: case BINOP_OR: case BINOP_XOR: case BINOP_CONCAT: {
if (type_eq(lhs_t, rhs_t) && binding_works(binop_method_names[binop->op], binop->lhs, lhs_t, rhs_t, lhs_t))
return lhs_t;
break;

View File

@ -494,6 +494,7 @@ PUREFUNC size_t unpadded_struct_size(type_t *t)
PUREFUNC size_t type_size(type_t *t)
{
if (t == THREAD_TYPE) return sizeof(pthread_t*);
if (t == PATH_TYPE) return sizeof(Path_t);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
switch (t->tag) {
@ -579,6 +580,7 @@ PUREFUNC size_t type_size(type_t *t)
PUREFUNC size_t type_align(type_t *t)
{
if (t == THREAD_TYPE) return __alignof__(pthread_t*);
if (t == PATH_TYPE) return __alignof__(Path_t);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
switch (t->tag) {