123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 | // This file defines how to compile text
#include <ctype.h>
#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/text.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"
public
Text_t expr_as_text(Text_t expr, type_t *t, Text_t color) {
switch (t->tag) {
case MemoryType: return Texts("Memory$as_text(stack(", expr, "), ", color, ", &Memory$info)");
case BoolType:
// NOTE: this cannot use stack(), since bools may actually be bit
// fields:
return Texts("Bool$as_text((Bool_t[1]){", expr, "}, ", color, ", &Bool$info)");
case CStringType: return Texts("CString$as_text(stack(", expr, "), ", color, ", &CString$info)");
case PathType: return Texts("Path$as_text(stack(", expr, "), ", color, ", &Path$info)");
case BigIntType:
case IntType:
case ByteType:
case NumType:
case FloatType: {
Text_t name = type_to_text(t);
return Texts(name, "$as_text(stack(", expr, "), ", color, ", &", name, "$info)");
}
case TextType: return Texts("Text$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case ListType: return Texts("List$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case TableType: return Texts("Table$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case FunctionType:
case ClosureType: return Texts("Func$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case PointerType: return Texts("Pointer$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case OptionalType: return Texts("Optional$as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
case StructType:
case EnumType: return Texts("generic_as_text(stack(", expr, "), ", color, ", ", compile_type_info(t), ")");
default: compiler_err(NULL, NULL, NULL, "Stringifying is not supported for ", type_to_text(t));
}
return EMPTY_TEXT;
}
public
Text_t compile_text(env_t *env, ast_t *ast) {
type_t *t = get_type(env, ast);
Text_t expr = compile(env, ast);
return expr_as_text(expr, t, Text("no"));
}
public
Text_t compile_text_literal(Text_t literal) {
Text_t code = Text("\"");
const char *utf8 = Text$as_c_string(literal);
for (const char *p = utf8; *p; p++) {
switch (*p) {
case '\\': code = Texts(code, "\\\\"); break;
case '"': code = Texts(code, "\\\""); break;
case '\a': code = Texts(code, "\\a"); break;
case '\b': code = Texts(code, "\\b"); break;
case '\n': code = Texts(code, "\\n"); break;
case '\r': code = Texts(code, "\\r"); break;
case '\t': code = Texts(code, "\\t"); break;
case '\v': code = Texts(code, "\\v"); break;
default: {
if (isprint(*p)) {
code = Texts(code, Text$from_strn(p, 1));
} else {
uint8_t byte = *(uint8_t *)p;
code = Texts(code, "\\x", String(hex(byte, .no_prefix = true, .uppercase = true, .digits = 2)), "\"\"");
}
break;
}
}
}
return Texts(code, "\"");
}
public
Text_t compile_bytes_literal(List_t bytes) {
Text_t code = Text("\"");
const uint8_t *data = (const uint8_t *)bytes.data;
for (int64_t i = 0; i < (int64_t)bytes.length; i++) {
uint8_t byte = data[i * bytes.stride];
switch (byte) {
case '\\': code = Texts(code, "\\\\"); break;
case '"': code = Texts(code, "\\\""); break;
case '\a': code = Texts(code, "\\a"); break;
case '\b': code = Texts(code, "\\b"); break;
case '\n': code = Texts(code, "\\n"); break;
case '\r': code = Texts(code, "\\r"); break;
case '\t': code = Texts(code, "\\t"); break;
case '\v': code = Texts(code, "\\v"); break;
default: {
if (isprint(byte) && byte < 0x80) {
code = Texts(code, Text$from_strn((char *)&byte, 1));
} else {
code = Texts(code, "\\x", String(hex(byte, .no_prefix = true, .uppercase = true, .digits = 2)), "\"\"");
}
break;
}
}
}
return Texts(code, "\"");
}
PUREFUNC static bool string_literal_is_all_ascii(Text_t literal) {
TextIter_t state = NEW_TEXT_ITER_STATE(literal);
for (int64_t i = 0; i < (int64_t)literal.length; i++) {
int32_t g = Text$get_grapheme_fast(&state, i);
if (g < 0 || g > 127 || !isascii(g)) return false;
}
return true;
}
static Text_t compile_file_bytes_as_text(ast_t *ast, List_t bytes, Path_t file) {
OptionalText_t text = Text$from_utf8(bytes);
if (text.tag == TEXT_NONE)
code_err(ast, "This file's contents are not valid UTF8, so it can't be used here as a Text value: ", file);
if (text.length == 0) return Text("EMPTY_TEXT");
if (string_literal_is_all_ascii(text)) return Texts("Text(", compile_text_literal(text), ")");
else return Texts("Text$from_str(", compile_text_literal(text), ")");
}
public
Text_t compile_embed_as_text(ast_t *ast, List_t bytes) {
return compile_file_bytes_as_text(ast, bytes, get_embed_path(ast));
}
static Text_t compile_file_bytes_as_cstring(ast_t *ast, List_t bytes, Path_t file) {
const uint8_t *data = (const uint8_t *)bytes.data;
for (int64_t i = 0; i < (int64_t)bytes.length; i++) {
if (data[i * bytes.stride] == 0)
code_err(ast, "This file has a NUL byte in it, so it can't be embedded as a CString: ", file);
}
return compile_bytes_literal(bytes);
}
public
Text_t compile_embed_as_cstring(ast_t *ast, List_t bytes) {
return compile_file_bytes_as_cstring(ast, bytes, get_embed_path(ast));
}
public
Text_t compile_embedded_bytes_list(List_t bytes) {
// This is a compile-time constant: a `List_t` whose `.data` points directly at a
// read-only string literal. `LIST_MAX_DATA_REFCOUNT` marks it as "shared", so any
// in-place mutation copies the data first instead of writing into read-only memory.
return Texts("((List_t){.length=", (int64_t)bytes.length, ", .stride=1, .data=(void*)",
compile_bytes_literal(bytes), ", .atomic=1, .data_refcount=LIST_MAX_DATA_REFCOUNT})");
}
// Compile a directory embed to a table of each contained file's contents,
// keyed by its path relative to the embedded directory. Keys are normalized
// at compile time, into the same form path literals compile to, so
// `dir[(./inner/file.txt)]` finds its entry with plain string equality.
public
Text_t compile_embed_as_table(ast_t *ast, type_t *value_type) {
Path_t dir = get_embed_path(ast);
if (!Path$is_directory(dir, true))
code_err(ast, "This is not a directory, so it can't be embedded as a table: ", dir);
if (!(value_type->tag == TextType || value_type->tag == CStringType
|| (value_type->tag == ListType && Match(value_type, ListType)->item_type->tag == ByteType)))
code_err(ast, "A directory can only be embedded as a {Path:[Byte]}, {Path:Text}, or {Path:CString} table, "
"not {Path:",
type_to_text(value_type), "}");
List_t files = get_dir_embed_files(ast, NULL);
Text_t code = Texts("Table(Path_t, ", compile_type(value_type), ", &Path$info, ", compile_type_info(value_type),
", /*fallback:*/ NULL, ", (int64_t)files.length);
for (int64_t i = 0; i < (int64_t)files.length; i++) {
Path_t file = *(Path_t *)(files.data + i * files.stride);
Path_t key = normalized_path(String("./", Path$relative_to(file, dir)));
OptionalList_t bytes = Path$read_bytes(file, NONE_INT);
if (bytes.data == NULL) code_err(ast, "I couldn't read this file: ", file);
Text_t value;
if (value_type->tag == TextType) value = compile_file_bytes_as_text(ast, bytes, file);
else if (value_type->tag == CStringType) value = compile_file_bytes_as_cstring(ast, bytes, file);
else value = compile_embedded_bytes_list(bytes);
code = Texts(code, ",\n\t{Path(", compile_text_literal(Text$from_str(key)), "), ", value, "}");
}
return Texts(code, ")");
}
public
Text_t compile_text_ast(env_t *env, ast_t *ast) {
if (ast->tag == TextLiteral) {
Text_t literal = Match(ast, TextLiteral)->text;
if (literal.length == 0) return Text("EMPTY_TEXT");
if (string_literal_is_all_ascii(literal)) return Texts("Text(", compile_text_literal(literal), ")");
else return Texts("Text$from_str(", compile_text_literal(literal), ")");
}
type_ast_t *lang = Match(ast, TextJoin)->lang;
type_t *text_t = lang ? parse_type_ast(env, lang) : TEXT_TYPE;
if (!text_t || text_t->tag != TextType) code_err(ast, "This is not a valid text type");
ast_list_t *chunks = Match(ast, TextJoin)->children;
if (!chunks) {
return Text("EMPTY_TEXT");
} else if (!chunks->next && chunks->ast->tag == TextLiteral) {
Text_t literal = Match(chunks->ast, TextLiteral)->text;
if (string_literal_is_all_ascii(literal)) return Texts("Text(", compile_text_literal(literal), ")");
return Texts("((", compile_type(text_t), ")", compile(env, chunks->ast), ")");
} else {
Text_t code = EMPTY_TEXT;
for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
Text_t chunk_code;
type_t *chunk_t = get_type(env, chunk->ast);
if (chunk->ast->tag == TextLiteral || type_eq(chunk_t, text_t)) {
chunk_code = compile(env, chunk->ast);
} else {
binding_t *constructor = get_constructor(env, text_t, new (arg_ast_t, .value = chunk->ast));
if (constructor) {
arg_t *arg_spec = Match(constructor->type, FunctionType)->args;
arg_ast_t *args = new (arg_ast_t, .value = chunk->ast);
chunk_code = Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, args), ")");
} else if (type_eq(text_t, TEXT_TYPE)) {
if (chunk_t->tag == TextType) chunk_code = compile(env, chunk->ast);
else if (chunk_t->tag == CStringType)
chunk_code = Texts("Text$from_str(", compile(env, chunk->ast), ")");
else chunk_code = compile_text(env, chunk->ast);
} else {
code_err(chunk->ast, "I don't know how to convert ", type_to_text(chunk_t), " to ",
type_to_text(text_t));
}
}
code = Texts(code, chunk_code);
if (chunk->next) code = Texts(code, ", ");
}
return Texts("Text$concat(", code, ")");
}
}
|