aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-15 15:33:47 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-15 15:33:47 -0400
commite422079fcced744e3a6247aeb12a09a658989072 (patch)
tree393d5e52ba67dcc822ccfa9a812198edda5e735d
parent259c7efcf8c3808d8151d8e15f1167ad2b6f2ca7 (diff)
Add a Byte datatype
-rw-r--r--Makefile2
-rw-r--r--ast.h2
-rw-r--r--compile.c89
-rw-r--r--docs/README.md2
-rw-r--r--docs/integers.md6
-rw-r--r--docs/operators.md6
-rw-r--r--docs/paths.md119
-rw-r--r--docs/text.md12
-rw-r--r--environment.c31
-rw-r--r--examples/http.tm2
-rw-r--r--parse.c10
-rw-r--r--stdlib/bytes.c38
-rw-r--r--stdlib/bytes.h29
-rw-r--r--stdlib/integers.c8
-rw-r--r--stdlib/paths.c60
-rw-r--r--stdlib/paths.h4
-rw-r--r--stdlib/tomo.h1
-rw-r--r--test/bytes.tm8
-rw-r--r--test/integers.tm16
-rw-r--r--test/lang.tm2
-rw-r--r--test/optionals.tm6
-rw-r--r--test/paths.tm2
-rw-r--r--test/text.tm16
-rw-r--r--typecheck.c10
-rw-r--r--types.c7
-rw-r--r--types.h2
26 files changed, 368 insertions, 122 deletions
diff --git a/Makefile b/Makefile
index d4694d53..3b21ebad 100644
--- a/Makefile
+++ b/Makefile
@@ -28,7 +28,7 @@ O=-Og
CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS)
CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')"
LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
-BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/channels.o stdlib/nums.o stdlib/integers.o \
+BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdlib/channels.o stdlib/nums.o stdlib/integers.o \
stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \
stdlib/types.o stdlib/util.o stdlib/files.o stdlib/ranges.o stdlib/shell.o stdlib/paths.o \
stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o
diff --git a/ast.h b/ast.h
index 55ac28fb..54a96d34 100644
--- a/ast.h
+++ b/ast.h
@@ -149,7 +149,7 @@ struct ast_s {
} Var;
struct {
const char *str;
- enum { IBITS_UNSPECIFIED=0, IBITS8=8, IBITS16=16, IBITS32=32, IBITS64=64 } bits;
+ enum { IBITS_UNSPECIFIED=0, IBITS_BYTE=7, IBITS8=8, IBITS16=16, IBITS32=32, IBITS64=64 } bits;
} Int;
struct {
double n;
diff --git a/compile.c b/compile.c
index 2802519a..a1f4d649 100644
--- a/compile.c
+++ b/compile.c
@@ -41,6 +41,8 @@ CORD promote_to_optional(type_t *t, CORD code)
case TYPE_IBITS64: return CORD_all("((OptionalInt64_t){", code, "})");
default: errx(1, "Unsupported in type: %T", t);
}
+ } else if (t->tag == ByteType) {
+ return CORD_all("((OptionalByte_t){", code, "})");
} else if (t->tag == StructType) {
return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){", code, "})");
} else {
@@ -202,6 +204,7 @@ CORD compile_type(type_t *t)
case VoidType: return "void";
case MemoryType: return "void";
case BoolType: return "Bool_t";
+ case ByteType: return "Byte_t";
case CStringType: return "char*";
case BigIntType: return "Int_t";
case IntType: return CORD_asprintf("Int%ld_t", Match(t, IntType)->bits);
@@ -239,6 +242,7 @@ CORD compile_type(type_t *t)
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case BoolType: return "OptionalBool_t";
+ case ByteType: return "OptionalByte_t";
case CStringType: case BigIntType: case NumType: case TextType:
case ArrayType: case SetType: case TableType: case FunctionType: case ClosureType:
case PointerType: case EnumType: case ChannelType:
@@ -366,9 +370,7 @@ static CORD check_null(type_t *t, CORD value)
return CORD_all("((", value, ") == NULL_BOOL)");
else if (t->tag == TextType)
return CORD_all("((", value, ").length < 0)");
- else if (t->tag == IntType)
- return CORD_all("(", value, ").is_null");
- else if (t->tag == StructType)
+ else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType)
return CORD_all("(", value, ").is_null");
else if (t->tag == EnumType)
return CORD_all("((", value, ").tag == 0)");
@@ -635,27 +637,27 @@ CORD compile_statement(env_t *env, ast_t *ast)
switch (update->op) {
case BINOP_MULT:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a multiply assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " *= ", rhs, ";");
case BINOP_DIVIDE:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a divide assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " /= ", rhs, ";");
case BINOP_MOD:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a mod assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " = ", lhs, " % ", rhs);
case BINOP_MOD1:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a mod assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " = (((", lhs, ") - 1) % ", rhs, ") + 1;");
case BINOP_PLUS:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do an addition assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " += ", rhs, ";");
case BINOP_MINUS:
- if (lhs_t->tag != IntType && lhs_t->tag != NumType)
+ if (lhs_t->tag != IntType && lhs_t->tag != NumType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a subtraction assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " -= ", rhs, ";");
case BINOP_POWER: {
@@ -667,17 +669,17 @@ CORD compile_statement(env_t *env, ast_t *ast)
return CORD_all(lhs, " = pow(", lhs, ", ", rhs, ");");
}
case BINOP_LSHIFT:
- if (lhs_t->tag != IntType)
+ if (lhs_t->tag != IntType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a shift assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " <<= ", rhs, ";");
case BINOP_RSHIFT:
- if (lhs_t->tag != IntType)
+ if (lhs_t->tag != IntType && lhs_t->tag != ByteType)
code_err(ast, "I can't do a shift assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " >>= ", rhs, ";");
case BINOP_AND: {
if (lhs_t->tag == BoolType)
return CORD_all("if (", lhs, ") ", lhs, " = ", rhs, ";");
- else if (lhs_t->tag == IntType)
+ else if (lhs_t->tag == IntType || lhs_t->tag == ByteType)
return CORD_all(lhs, " &= ", rhs, ";");
else
code_err(ast, "'or=' is not implemented for %T types", lhs_t);
@@ -685,13 +687,13 @@ CORD compile_statement(env_t *env, ast_t *ast)
case BINOP_OR: {
if (lhs_t->tag == BoolType)
return CORD_all("if (!(", lhs, ")) ", lhs, " = ", rhs, ";");
- else if (lhs_t->tag == IntType)
+ else if (lhs_t->tag == IntType || lhs_t->tag == ByteType)
return CORD_all(lhs, " |= ", rhs, ";");
else
code_err(ast, "'or=' is not implemented for %T types", lhs_t);
}
case BINOP_XOR:
- if (lhs_t->tag != IntType && lhs_t->tag != BoolType)
+ if (lhs_t->tag != IntType && lhs_t->tag != BoolType && lhs_t->tag != ByteType)
code_err(ast, "I can't do an xor assignment with this operator between %T and %T", lhs_t, rhs_t);
return CORD_all(lhs, " ^= ", rhs, ";");
case BINOP_CONCAT: {
@@ -1398,11 +1400,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color)
// NOTE: this cannot use stack(), since bools may actually be bit fields:
return CORD_asprintf("Bool$as_text((Bool_t[1]){%r}, %r, &Bool$info)", expr, color);
case CStringType: return CORD_asprintf("CString$as_text(stack(%r), %r, &CString$info)", expr, color);
- case BigIntType: case IntType: {
- CORD name = type_to_cord(t);
- return CORD_asprintf("%r$as_text(stack(%r), %r, &%r$info)", name, expr, color, name);
- }
- case NumType: {
+ case BigIntType: case IntType: case ByteType: case NumType: {
CORD name = type_to_cord(t);
return CORD_asprintf("%r$as_text(stack(%r), %r, &%r$info)", name, expr, color, name);
}
@@ -1757,6 +1755,7 @@ CORD compile_null(type_t *t)
break;
}
case BoolType: return "NULL_BOOL";
+ case ByteType: return "NULL_BYTE";
case ArrayType: return "NULL_ARRAY";
case TableType: return "NULL_TABLE";
case SetType: return "NULL_TABLE";
@@ -1825,21 +1824,25 @@ CORD compile(env_t *env, ast_t *ast)
return CORD_asprintf("Int$from_str(\"%s\")", str);
}
case IBITS64:
- if ((mpz_cmp_si(i, INT64_MAX) < 0) && (mpz_cmp_si(i, INT64_MIN) > 0))
+ if ((mpz_cmp_si(i, INT64_MAX) <= 0) && (mpz_cmp_si(i, INT64_MIN) >= 0))
return CORD_asprintf("I64(%ldl)", mpz_get_si(i));
code_err(ast, "This value cannot fit in a 64-bit integer");
case IBITS32:
- if ((mpz_cmp_si(i, INT32_MAX) < 0) && (mpz_cmp_si(i, INT32_MIN) > 0))
+ if ((mpz_cmp_si(i, INT32_MAX) <= 0) && (mpz_cmp_si(i, INT32_MIN) >= 0))
return CORD_asprintf("I32(%ld)", mpz_get_si(i));
code_err(ast, "This value cannot fit in a 32-bit integer");
case IBITS16:
- if ((mpz_cmp_si(i, INT16_MAX) < 0) && (mpz_cmp_si(i, INT16_MIN) > 0))
+ if ((mpz_cmp_si(i, INT16_MAX) <= 0) && (mpz_cmp_si(i, INT16_MIN) >= 0))
return CORD_asprintf("I16(%ld)", mpz_get_si(i));
code_err(ast, "This value cannot fit in a 16-bit integer");
case IBITS8:
- if ((mpz_cmp_si(i, INT8_MAX) < 0) && (mpz_cmp_si(i, INT8_MIN) > 0))
+ if ((mpz_cmp_si(i, INT8_MAX) <= 0) && (mpz_cmp_si(i, INT8_MIN) >= 0))
return CORD_asprintf("I8(%ld)", mpz_get_si(i));
code_err(ast, "This value cannot fit in a 8-bit integer");
+ case IBITS_BYTE:
+ if ((mpz_cmp_si(i, UINT8_MAX) <= 0) && (mpz_cmp_si(i, 0) >= 0))
+ return CORD_asprintf("Byte(%ld)", mpz_get_si(i));
+ code_err(ast, "This value cannot fit in a byte");
default: code_err(ast, "Not a valid integer bit width");
}
}
@@ -1865,7 +1868,7 @@ CORD compile(env_t *env, ast_t *ast)
if (t->tag == BoolType)
return CORD_all("!(", compile(env, value), ")");
- else if (t->tag == IntType)
+ else if (t->tag == IntType || t->tag == ByteType)
return CORD_all("~(", compile(env, value), ")");
else if (t->tag == ArrayType)
return CORD_all("((", compile(env, value), ").length == 0)");
@@ -1948,42 +1951,42 @@ CORD compile(env_t *env, ast_t *ast)
return CORD_all("pow(", lhs, ", ", rhs, ")");
}
case BINOP_MULT: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " * ", rhs, ")");
}
case BINOP_DIVIDE: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " / ", rhs, ")");
}
case BINOP_MOD: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " % ", rhs, ")");
}
case BINOP_MOD1: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("((((", lhs, ")-1) % (", rhs, ")) + 1)");
}
case BINOP_PLUS: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " + ", rhs, ")");
}
case BINOP_MINUS: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " - ", rhs, ")");
}
case BINOP_LSHIFT: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " << ", rhs, ")");
}
case BINOP_RSHIFT: {
- if (operand_t->tag != IntType && operand_t->tag != NumType)
+ if (operand_t->tag != IntType && operand_t->tag != NumType && operand_t->tag != ByteType)
code_err(ast, "Math operations are only supported for numeric types");
return CORD_all("(", lhs, " >> ", rhs, ")");
}
@@ -1991,7 +1994,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("Int$equal_value(", lhs, ", ", rhs, ")");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " == ", rhs, ")");
default:
return CORD_asprintf("generic_equal(stack(%r), stack(%r), %r)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2001,7 +2004,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("!Int$equal_value(", lhs, ", ", rhs, ")");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " != ", rhs, ")");
default:
return CORD_asprintf("!generic_equal(stack(%r), stack(%r), %r)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2011,7 +2014,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("(Int$compare_value(", lhs, ", ", rhs, ") < 0)");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " < ", rhs, ")");
default:
return CORD_asprintf("(generic_compare(stack(%r), stack(%r), %r) < 0)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2021,7 +2024,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("(Int$compare_value(", lhs, ", ", rhs, ") <= 0)");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " <= ", rhs, ")");
default:
return CORD_asprintf("(generic_compare(stack(%r), stack(%r), %r) <= 0)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2031,7 +2034,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("(Int$compare_value(", lhs, ", ", rhs, ") > 0)");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " > ", rhs, ")");
default:
return CORD_asprintf("(generic_compare(stack(%r), stack(%r), %r) > 0)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2041,7 +2044,7 @@ CORD compile(env_t *env, ast_t *ast)
switch (operand_t->tag) {
case BigIntType:
return CORD_all("(Int$compare_value(", lhs, ", ", rhs, ") >= 0)");
- case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
+ case BoolType: case ByteType: case IntType: case NumType: case PointerType: case FunctionType:
return CORD_all("(", lhs, " >= ", rhs, ")");
default:
return CORD_asprintf("(generic_compare(stack(%r), stack(%r), %r) >= 0)", lhs, rhs, compile_type_info(env, operand_t));
@@ -2050,7 +2053,7 @@ CORD compile(env_t *env, ast_t *ast)
case BINOP_AND: {
if (operand_t->tag == BoolType)
return CORD_all("(", lhs, " && ", rhs, ")");
- else if (operand_t->tag == IntType)
+ else if (operand_t->tag == IntType || operand_t->tag == ByteType)
return CORD_all("(", lhs, " & ", rhs, ")");
else
code_err(ast, "Boolean operators are only supported for Bool and integer types");
@@ -2061,13 +2064,13 @@ CORD compile(env_t *env, ast_t *ast)
case BINOP_OR: {
if (operand_t->tag == BoolType)
return CORD_all("(", lhs, " || ", rhs, ")");
- else if (operand_t->tag == IntType)
+ else if (operand_t->tag == IntType || operand_t->tag == ByteType)
return CORD_all("(", lhs, " | ", rhs, ")");
else
code_err(ast, "Boolean operators are only supported for Bool and integer types");
}
case BINOP_XOR: {
- if (operand_t->tag == BoolType || operand_t->tag == IntType)
+ if (operand_t->tag == BoolType || operand_t->tag == IntType || operand_t->tag == ByteType)
return CORD_all("(", lhs, " ^ ", rhs, ")");
else
code_err(ast, "Boolean operators are only supported for Bool and integer types");
@@ -2200,7 +2203,7 @@ CORD compile(env_t *env, ast_t *ast)
CORD comparison;
if (key_t->tag == BigIntType)
comparison = CORD_all("(Int$compare_value(", lhs_key, ", ", rhs_key, ")", (ast->tag == Min ? "<=" : ">="), "0)");
- else if (key_t->tag == IntType || key_t->tag == NumType || key_t->tag == BoolType || key_t->tag == PointerType)
+ else if (key_t->tag == IntType || key_t->tag == NumType || key_t->tag == BoolType || key_t->tag == PointerType || key_t->tag == ByteType)
comparison = CORD_all("((", lhs_key, ")", (ast->tag == Min ? "<=" : ">="), "(", rhs_key, "))");
else
comparison = CORD_all("generic_compare(stack(", lhs_key, "), stack(", rhs_key, "), ", compile_type_info(env, key_t), ")",
@@ -3277,7 +3280,7 @@ CORD compile_namespace_definitions(env_t *env, const char *ns_name, ast_t *block
CORD compile_type_info(env_t *env, type_t *t)
{
switch (t->tag) {
- case BoolType: case IntType: case BigIntType: case NumType: case CStringType:
+ case BoolType: case ByteType: case IntType: case BigIntType: case NumType: case CStringType:
return CORD_all("&", type_to_cord(t), "$info");
case TextType: {
auto text = Match(t, TextType);
diff --git a/docs/README.md b/docs/README.md
index 763f863a..642293ce 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -76,7 +76,7 @@ Exits the program with a given status and optionally prints a message.
**Usage:**
```markdown
-ask(message:Text = "", status:Int32 = 0_i32) -> Void
+ask(message:Text = "", status:Int32 = 0[32]) -> Void
```
**Parameters:**
diff --git a/docs/integers.md b/docs/integers.md
index 50ecd034..1b25bcf0 100644
--- a/docs/integers.md
+++ b/docs/integers.md
@@ -8,9 +8,9 @@ Tomo has five types of integers:
the GNU MP library. These integers are fast for small numbers and guaranteed
to always be correct and never overflow.
- `Int8`/`Int16`/`Int32`/`Int64`: Fixed-size integers that take up `N` bits.
- These integers must be manually specified with their suffix (e.g. `5_i64`)
- and are subject to overflowing. If an overflow occurs, a runtime error will
- be raised.
+ These integers must be manually specified with their bits in square brackets
+ (e.g. `5[64]`) and are subject to overflowing. If an overflow occurs, a
+ runtime error will be raised.
Conversion between integer types can be done by calling the target type as a
function: `Int32(x)`. For fixed-width types, the conversion function also
diff --git a/docs/operators.md b/docs/operators.md
index a3c9e01e..85e53242 100644
--- a/docs/operators.md
+++ b/docs/operators.md
@@ -25,11 +25,11 @@ value to the user.
```tomo
>> 0 <> 99
-= -1_i32
+= -1[32]
>> 5 <> 5
-= 0_i32
+= 0[32]
>> 99 <> 0
-= 1_i32
+= 1[32]
```
It's particularly handy for using the array `sort()` method, which takes a
diff --git a/docs/paths.md b/docs/paths.md
index de483168..6408cc8a 100644
--- a/docs/paths.md
+++ b/docs/paths.md
@@ -40,11 +40,12 @@ intended. Paths can be created from text with slashes using
### `append`
**Description:**
-Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error.
+Appends the given text to the file at the specified path, creating the file if
+it doesn't already exist. Failure to write will result in a runtime error.
**Usage:**
```markdown
-append(path: Path, text: Text, permissions: Int32 = 0o644_i32) -> Void
+append(path: Path, text: Text, permissions: Int32 = 0o644[32]) -> Void
```
**Parameters:**
@@ -63,6 +64,33 @@ Nothing.
---
+### `append_bytes`
+
+**Description:**
+Appends the given bytes to the file at the specified path, creating the file if
+it doesn't already exist. Failure to write will result in a runtime error.
+
+**Usage:**
+```markdown
+append_bytes(path: Path, bytes: [Byte], permissions: Int32 = 0o644[32]) -> Void
+```
+
+**Parameters:**
+
+- `path`: The path of the file to append to.
+- `bytes`: The bytes to append to the file.
+- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`).
+
+**Returns:**
+Nothing.
+
+**Example:**
+```markdown
+(./log.txt):append_bytes([104[B], 105[B]])
+```
+
+---
+
### `base_name`
**Description:**
@@ -146,7 +174,7 @@ Creates a new directory at the specified path with the given permissions.
**Usage:**
```markdown
-create_directory(path: Path, permissions=0o644_i32) -> Void
+create_directory(path: Path, permissions=0o644[32]) -> Void
```
**Parameters:**
@@ -404,6 +432,29 @@ The contents of the file. If the file does not exist, an error will be raised.
```markdown
content := (./file.txt):read()
```
+---
+
+### `read_bytes`
+
+**Description:**
+Reads the contents of the file at the specified path.
+
+**Usage:**
+```markdown
+read_bytes(path: Path) -> [Byte]
+```
+
+**Parameters:**
+
+- `path`: The path of the file to read.
+
+**Returns:**
+The byte contents of the file. If the file does not exist, an error will be raised.
+
+**Example:**
+```markdown
+content := (./file.txt):read_bytes()
+```
---
@@ -554,7 +605,7 @@ writing cannot be successfully completed, a runtime error is raised.
**Usage:**
```markdown
-write(path: Path, text: Text, permissions=0o644_i32) -> Void
+write(path: Path, text: Text, permissions=0o644[32]) -> Void
```
**Parameters:**
@@ -573,6 +624,34 @@ Nothing.
---
+### `write_bytes`
+
+**Description:**
+Writes the given bytes to the file at the specified path, creating the file if
+it doesn't already exist. Sets the file permissions as specified. If the file
+writing cannot be successfully completed, a runtime error is raised.
+
+**Usage:**
+```markdown
+write(path: Path, bytes: [Byte], permissions=0o644[32]) -> Void
+```
+
+**Parameters:**
+
+- `path`: The path of the file to write to.
+- `bytes`: An array of bytes to write to the file.
+- `permissions` (optional): The permissions to set on the file if it is created (default is `0o644`).
+
+**Returns:**
+Nothing.
+
+**Example:**
+```markdown
+(./file.txt):write_bytes([104[B], 105[B]])
+```
+
+---
+
### `write_unique`
**Description:**
@@ -602,3 +681,35 @@ The path of the newly created unique file.
= "Hello, world!"
created:remove()
```
+
+---
+
+### `write_unique_bytes`
+
+**Description:**
+Writes the given bytes to a unique file path based on the specified path. The
+file is created if it doesn't exist. This is useful for creating temporary
+files.
+
+**Usage:**
+```markdown
+write_unique_bytes(path: Path, bytes: [Byte]) -> Path
+```
+
+**Parameters:**
+
+- `path`: The base path for generating the unique file. This path must include
+ the string `XXXXXX` in the file base name.
+- `bytes`: The bytes to write to the file.
+
+**Returns:**
+The path of the newly created unique file.
+
+**Example:**
+```markdown
+>> created := (./file-XXXXXX.txt):write_unique_bytes([1[B], 2[B], 3[B]])
+= (./file-27QHtq.txt)
+>> created:read()
+= [1[B], 2[B], 3[B]]
+created:remove()
+```
diff --git a/docs/text.md b/docs/text.md
index 18960b02..0b2b5709 100644
--- a/docs/text.md
+++ b/docs/text.md
@@ -441,7 +441,7 @@ An array of bytes (`[Int8]`) representing the text in UTF8 encoding.
**Example:**
```tomo
>> "Amélie":utf8_bytes()
-= [65_i8, 109_i8, -61_i8, -87_i8, 108_i8, 105_i8, 101_i8] : [Int8]
+= [65[B], 109[B], 195[B], 169[B], 108[B], 105[B], 101[B]] : [Byte]
```
---
@@ -491,7 +491,7 @@ An array of 32-bit integer Unicode code points (`[Int32]`).
**Example:**
```tomo
>> "Amélie":utf32_codepoints()
-= [65_i32, 109_i32, 233_i32, 108_i32, 105_i32, 101_i32] : [Int32]
+= [65[32], 109[32], 233[32], 108[32], 105[32], 101[32]] : [Int32]
```
---
@@ -601,7 +601,7 @@ A new text with the specified codepoints after normalization has been applied.
**Example:**
```tomo
->> Text.from_codepoints([197_i32, 107_i32, 101_i32])
+>> Text.from_codepoints([197[32], 107[32], 101[32]])
= "Åke"
```
@@ -628,7 +628,7 @@ A new text based on the input UTF8 bytes after normalization has been applied.
**Example:**
```tomo
->> Text.from_bytes([-61_i8, -123_i8, 107_i8, 101_i8])
+>> Text.from_bytes([195[B], 133[B], 107[B], 101[B]])
= "Åke"
```
@@ -668,11 +668,11 @@ found.
>> " one two three ":find("{id}", start=5)
= 8
->> len := 0_i64
+>> len := 0[64]
>> " one ":find("{id}", length=&len)
= 4
>> len
-= 3_i64
+= 3[64]
```
---
diff --git a/environment.c b/environment.c
index c0c85388..e0781468 100644
--- a/environment.c
+++ b/environment.c
@@ -88,6 +88,11 @@ env_t *new_compilation_unit(CORD *libname)
{"from_text", "Bool$from_text", "func(text:Text)->Bool?"},
{"random", "Bool$random", "func(p=0.5)->Bool"},
)},
+ {"Byte", Type(ByteType), "Byte_t", "Byte$info", TypedArray(ns_entry_t,
+ {"max", "Byte$max", "Byte"},
+ {"min", "Byte$min", "Byte"},
+ {"random", "Byte$random", "func(min=Byte.min, max=Byte.max)->Byte"},
+ )},
{"Int", Type(BigIntType), "Int_t", "Int$info", TypedArray(ns_entry_t,
{"abs", "Int$abs", "func(x:Int)->Int"},
{"bit_and", "Int$bit_and", "func(x:Int,y:Int)->Int"},
@@ -129,8 +134,9 @@ env_t *new_compilation_unit(CORD *libname)
{"modulo", "Int64$modulo", "func(x:Int64,y:Int64)->Int64"},
{"modulo1", "Int64$modulo1", "func(x:Int64,y:Int64)->Int64"},
{"octal", "Int64$octal", "func(i:Int64, digits=0, prefix=yes)->Text"},
- {"random", "Int64$random", "func(min=-0x8000000000000000_i64, max=0x7FFFFFFFFFFFFFFF_i64)->Int64"},
{"to", "Int64$to", "func(from:Int64,to:Int64)->Range"},
+ // Must be defined after min/max:
+ {"random", "Int64$random", "func(min=Int64.min, max=Int64.max)->Int64"},
)},
{"Int32", Type(IntType, .bits=TYPE_IBITS32), "Int32_t", "Int32$info", TypedArray(ns_entry_t,
{"abs", "abs", "func(i:Int32)->Int32"},
@@ -145,8 +151,9 @@ env_t *new_compilation_unit(CORD *libname)
{"modulo", "Int32$modulo", "func(x:Int32,y:Int32)->Int32"},
{"modulo1", "Int32$modulo1", "func(x:Int32,y:Int32)->Int32"},
{"octal", "Int32$octal", "func(i:Int32, digits=0, prefix=yes)->Text"},
- {"random", "Int32$random", "func(min=-0x80000000_i32, max=0x7FFFFFFF_i32)->Int32"},
{"to", "Int32$to", "func(from:Int32,to:Int32)->Range"},
+ // Must be defined after min/max:
+ {"random", "Int32$random", "func(min=Int32.min, max=Int32.max)->Int32"},
)},
{"Int16", Type(IntType, .bits=TYPE_IBITS16), "Int16_t", "Int16$info", TypedArray(ns_entry_t,
{"abs", "abs", "func(i:Int16)->Int16"},
@@ -161,8 +168,9 @@ env_t *new_compilation_unit(CORD *libname)
{"modulo", "Int16$modulo", "func(x:Int16,y:Int16)->Int16"},
{"modulo1", "Int16$modulo1", "func(x:Int16,y:Int16)->Int16"},
{"octal", "Int16$octal", "func(i:Int16, digits=0, prefix=yes)->Text"},
- {"random", "Int16$random", "func(min=-0x8000_i16, max=0x7FFF_i16)->Int16"},
{"to", "Int16$to", "func(from:Int16,to:Int16)->Range"},
+ // Must be defined after min/max:
+ {"random", "Int16$random", "func(min=Int16.min, max=Int16.max)->Int16"},
)},
{"Int8", Type(IntType, .bits=TYPE_IBITS8), "Int8_t", "Int8$info", TypedArray(ns_entry_t,
{"abs", "abs", "func(i:Int8)->Int8"},
@@ -177,8 +185,9 @@ env_t *new_compilation_unit(CORD *libname)
{"modulo", "Int8$modulo", "func(x:Int8,y:Int8)->Int8"},
{"modulo1", "Int8$modulo1", "func(x:Int8,y:Int8)->Int8"},
{"octal", "Int8$octal", "func(i:Int8, digits=0, prefix=yes)->Text"},
- {"random", "Int8$random", "func(min=-0x80_i8, max=0x7F_i8)->Int8"},
{"to", "Int8$to", "func(from:Int8,to:Int8)->Range"},
+ // Must be defined after min/max:
+ {"random", "Int8$random", "func(min=Int8.min, max=Int8.max)->Int8"},
)},
#define C(name) {#name, "M_"#name, "Num"}
#define F(name) {#name, #name, "func(n:Num)->Num"}
@@ -250,11 +259,12 @@ env_t *new_compilation_unit(CORD *libname)
{"escape_text", "Pattern$escape_text", "func(text:Text)->Pattern"},
)},
{"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t,
- {"append", "Path$append", "func(path:Path, text:Text, permissions=0o644_i32)"},
+ {"append", "Path$append", "func(path:Path, text:Text, permissions=0o644[32])"},
+ {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=0o644[32])"},
{"base_name", "Path$base_name", "func(path:Path)->Text"},
{"by_line", "Path$by_line", "func(path:Path)->func()->Text?"},
{"children", "Path$children", "func(path:Path, include_hidden=no)->[Path]"},
- {"create_directory", "Path$create_directory", "func(path:Path, permissions=0o644_i32)"},
+ {"create_directory", "Path$create_directory", "func(path:Path, permissions=0o644[32])"},
{"escape_int", "Int$value_as_text", "func(i:Int)->Path"},
{"escape_path", "Path$escape_path", "func(path:Path)->Path"},
{"escape_text", "Path$escape_text", "func(text:Text)->Path"},
@@ -268,13 +278,16 @@ env_t *new_compilation_unit(CORD *libname)
{"is_symlink", "Path$is_symlink", "func(path:Path)->Bool"},
{"parent", "Path$parent", "func(path:Path)->Path"},
{"read", "Path$read", "func(path:Path)->Text"},
+ {"read_bytes", "Path$read_bytes", "func(path:Path)->[Byte]"},
{"relative", "Path$relative", "func(path:Path, relative_to=(./))->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]"},
{"unique_directory", "Path$unique_directory", "func(path:Path)->Path"},
- {"write", "Path$write", "func(path:Path, text:Text, permissions=0o644_i32)"},
+ {"write", "Path$write", "func(path:Path, text:Text, permissions=0o644[32])"},
+ {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=0o644[32])"},
{"write_unique", "Path$write_unique", "func(path:Path, text:Text)->Path"},
+ {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte])->Path"},
)},
{"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t,
{"escape_int", "Int$value_as_text", "func(i:Int)->Shell"},
@@ -287,7 +300,7 @@ env_t *new_compilation_unit(CORD *libname)
{"ends_with", "Text$ends_with", "func(text:Text, suffix:Text)->Bool"},
{"find", "Text$find", "func(text:Text, pattern:Pattern, start=1, length=!&Int64)->Int"},
{"find_all", "Text$find_all", "func(text:Text, pattern:Pattern)->[Text]"},
- {"from_bytes", "Text$from_bytes", "func(bytes:[Int8])->Text"},
+ {"from_bytes", "Text$from_bytes", "func(bytes:[Byte])->Text"},
{"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"},
@@ -309,7 +322,7 @@ env_t *new_compilation_unit(CORD *libname)
{"trim", "Text$trim", "func(text:Text, pattern=$/{whitespace}/, trim_left=yes, trim_right=yes)->Text"},
{"upper", "Text$upper", "func(text:Text)->Text"},
{"utf32_codepoints", "Text$utf32_codepoints", "func(text:Text)->[Int32]"},
- {"utf8_bytes", "Text$utf8_bytes", "func(text:Text)->[Int8]"},
+ {"utf8_bytes", "Text$utf8_bytes", "func(text:Text)->[Byte]"},
)},
{"Thread", THREAD_TYPE, "pthread_t*", "Thread", TypedArray(ns_entry_t,
{"new", "Thread$new", "func(fn:func())->Thread"},
diff --git a/examples/http.tm b/examples/http.tm
index 9590215e..0a27ece2 100644
--- a/examples/http.tm
+++ b/examples/http.tm
@@ -58,7 +58,7 @@ func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse:
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
- code := 0_i64
+ code := 0[64]
inline C {
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
diff --git a/parse.c b/parse.c
index a5ad136b..34c021c6 100644
--- a/parse.c
+++ b/parse.c
@@ -506,12 +506,12 @@ PARSER(parse_int) {
return NewAST(ctx->file, start, pos, Num, .n=n, .bits=64);
}
- match(&pos, "_");
auto bits = IBITS_UNSPECIFIED;
- if (match(&pos, "i64")) bits = IBITS64;
- else if (match(&pos, "i32")) bits = IBITS32;
- else if (match(&pos, "i16")) bits = IBITS16;
- else if (match(&pos, "i8")) bits = IBITS8;
+ if (match(&pos, "[64]")) bits = IBITS64;
+ else if (match(&pos, "[32]")) bits = IBITS32;
+ else if (match(&pos, "[16]")) bits = IBITS16;
+ else if (match(&pos, "[8]")) bits = IBITS8;
+ else if (match(&pos, "[B]")) bits = IBITS_BYTE;
// else if (match(&pos, ".") || match(&pos, "e")) return NULL; // looks like a float
diff --git a/stdlib/bytes.c b/stdlib/bytes.c
new file mode 100644
index 00000000..5b471bfe
--- /dev/null
+++ b/stdlib/bytes.c
@@ -0,0 +1,38 @@
+// The logic for unsigned bytes
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "bytes.h"
+#include "stdlib.h"
+#include "text.h"
+#include "util.h"
+
+public const Byte_t Byte$min = 0;
+public const Byte_t Byte$max = UINT8_MAX;
+
+PUREFUNC public Text_t Byte$as_text(const Byte_t *b, bool colorize, const TypeInfo *type)
+{
+ (void)type;
+ if (!b) return Text("Byte");
+ return Text$format(colorize ? "\x1b[35m%u[B]\x1b[m" : "%u[B]", *b);
+}
+
+public Byte_t Byte$random(Byte_t min, Byte_t max)
+{
+ if (min > max)
+ fail("Random minimum value (%u) is larger than the maximum value (%u)", min, max);
+ if (min == max)
+ return min;
+
+ uint32_t r = arc4random_uniform((uint32_t)max - (uint32_t)min + 1u);
+ return (Byte_t)(min + r);
+}
+
+public const TypeInfo Byte$info = {
+ .size=sizeof(Byte_t),
+ .align=__alignof__(Byte_t),
+ .tag=CustomInfo,
+ .CustomInfo={.as_text=(void*)Byte$as_text},
+};
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/bytes.h b/stdlib/bytes.h
new file mode 100644
index 00000000..d74ab80c
--- /dev/null
+++ b/stdlib/bytes.h
@@ -0,0 +1,29 @@
+#pragma once
+
+// An unsigned byte datatype
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "types.h"
+#include "util.h"
+
+#define Byte_t uint8_t
+#define Byte(b) ((Byte_t)(b))
+
+PUREFUNC Text_t Byte$as_text(const Byte_t *b, bool colorize, const TypeInfo *type);
+Byte_t Byte$random(Byte_t min, Byte_t max);
+
+extern const Byte_t Byte$min;
+extern const Byte_t Byte$max;
+
+extern const TypeInfo Byte$info;
+
+typedef struct {
+ Byte_t value;
+ bool is_null:1;
+} OptionalByte_t;
+
+#define NULL_BYTE ((OptionalByte_t){.is_null=true})
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/integers.c b/stdlib/integers.c
index ef588984..6caac31e 100644
--- a/stdlib/integers.c
+++ b/stdlib/integers.c
@@ -481,10 +481,10 @@ public const TypeInfo Int$info = {
.CustomInfo={.compare=(void*)KindOfInt##$compare, .as_text=(void*)KindOfInt##$as_text}, \
};
-DEFINE_INT_TYPE(int64_t, Int64, "ld_i64", INT64_MIN, INT64_MAX)
-DEFINE_INT_TYPE(int32_t, Int32, "d_i32", INT32_MIN, INT32_MAX)
-DEFINE_INT_TYPE(int16_t, Int16, "d_i16", INT16_MIN, INT16_MAX)
-DEFINE_INT_TYPE(int8_t, Int8, "d_i8", INT8_MIN, INT8_MAX)
+DEFINE_INT_TYPE(int64_t, Int64, "ld[64]", INT64_MIN, INT64_MAX)
+DEFINE_INT_TYPE(int32_t, Int32, "d[32]", INT32_MIN, INT32_MAX)
+DEFINE_INT_TYPE(int16_t, Int16, "d[16]", INT16_MIN, INT16_MAX)
+DEFINE_INT_TYPE(int8_t, Int8, "d[8]", INT8_MIN, INT8_MAX)
#undef DEFINE_INT_TYPE
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/paths.c b/stdlib/paths.c
index 231a7c23..bebb9389 100644
--- a/stdlib/paths.c
+++ b/stdlib/paths.c
@@ -207,7 +207,7 @@ public bool Path$is_symlink(Path_t path)
return (sb.st_mode & S_IFMT) == S_IFLNK;
}
-static void _write(Path_t path, Text_t text, int mode, int permissions)
+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);
@@ -215,24 +215,36 @@ static void _write(Path_t path, Text_t text, int mode, int permissions)
if (fd == -1)
fail("Could not write to file: %s\n%s", path_str, strerror(errno));
- const char *str = Text$as_c_string(text);
- size_t len = strlen(str);
- ssize_t written = write(fd, str, len);
- if (written != (ssize_t)len)
+ if (bytes.stride != 1)
+ Array$compact(&bytes, 1);
+ 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", path_str, strerror(errno));
}
public void Path$write(Path_t path, Text_t text, int permissions)
{
- _write(path, text, O_WRONLY | O_CREAT, permissions);
+ Array_t bytes = Text$utf8_bytes(text);
+ _write(path, bytes, O_WRONLY | O_CREAT, permissions);
+}
+
+public void Path$write_bytes(Path_t path, Array_t bytes, int permissions)
+{
+ _write(path, bytes, O_WRONLY | O_CREAT, permissions);
}
public void Path$append(Path_t path, Text_t text, int permissions)
{
- _write(path, text, O_WRONLY | O_APPEND | O_CREAT, permissions);
+ Array_t bytes = Text$utf8_bytes(text);
+ _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
-public Text_t Path$read(Path_t path)
+public void Path$append_bytes(Path_t path, Array_t bytes, int permissions)
+{
+ _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
+}
+
+public Array_t Path$read_bytes(Path_t path)
{
path = Path$_expand_home(path);
int fd = open(Text$as_c_string(path), O_RDONLY);
@@ -245,11 +257,11 @@ public Text_t Path$read(Path_t path)
if ((sb.st_mode & S_IFMT) == S_IFREG) { // Use memory mapping if it's a real file:
const char *mem = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- char *gc_mem = GC_MALLOC_ATOMIC((size_t)sb.st_size+1);
- memcpy(gc_mem, mem, (size_t)sb.st_size);
- gc_mem[sb.st_size] = '\0';
+ char *content = GC_MALLOC_ATOMIC((size_t)sb.st_size+1);
+ memcpy(content, mem, (size_t)sb.st_size);
+ content[sb.st_size] = '\0';
close(fd);
- return Text$from_strn(gc_mem, (size_t)sb.st_size);
+ return (Array_t){.data=content, .atomic=1, .stride=1, .length=(int64_t)sb.st_size};
} else {
size_t capacity = 256, len = 0;
char *content = GC_MALLOC_ATOMIC(capacity);
@@ -279,10 +291,16 @@ public Text_t Path$read(Path_t path)
if (u8_check((uint8_t*)content, len) != NULL)
fail("File does not contain valid UTF8 data!");
- return Text$from_strn(content, len);
+ return (Array_t){.data=content, .atomic=1, .stride=1, .length=len};
}
}
+public Text_t Path$read(Path_t path)
+{
+ Array_t bytes = Path$read_bytes(path);
+ return Text$from_bytes(bytes);
+}
+
public void Path$remove(Path_t path, bool ignore_missing)
{
path = Path$_expand_home(path);
@@ -375,7 +393,7 @@ public Path_t Path$unique_directory(Path_t path)
return Text$format("%s/", created);
}
-public Text_t Path$write_unique(Path_t path, Text_t text)
+public Text_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);
@@ -392,14 +410,20 @@ public Text_t Path$write_unique(Path_t path, Text_t text)
if (fd == -1)
fail("Could not write to unique file: %s\n%s", buf, strerror(errno));
- const char *str = Text$as_c_string(text);
- size_t write_len = strlen(str);
- ssize_t written = write(fd, str, write_len);
- if (written != (ssize_t)write_len)
+ if (bytes.stride != 1)
+ Array$compact(&bytes, 1);
+
+ 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);
}
+public Text_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("/../")));
diff --git a/stdlib/paths.h b/stdlib/paths.h
index e0d85258..d06d4fdc 100644
--- a/stdlib/paths.h
+++ b/stdlib/paths.h
@@ -26,8 +26,11 @@ bool Path$is_pipe(Path_t path, bool follow_symlinks);
bool Path$is_socket(Path_t path, bool follow_symlinks);
bool Path$is_symlink(Path_t path);
void Path$write(Path_t path, Text_t text, int permissions);
+void Path$write_bytes(Path_t path, Array_t bytes, int permissions);
void Path$append(Path_t path, Text_t text, int permissions);
+void Path$append_bytes(Path_t path, Array_t bytes, int permissions);
Text_t Path$read(Path_t path);
+Array_t Path$read_bytes(Path_t path);
void Path$remove(Path_t path, bool ignore_missing);
void Path$create_directory(Path_t path, int permissions);
Array_t Path$children(Path_t path, bool include_hidden);
@@ -35,6 +38,7 @@ 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$parent(Path_t path);
Text_t Path$base_name(Path_t path);
Text_t Path$extension(Path_t path, bool full);
diff --git a/stdlib/tomo.h b/stdlib/tomo.h
index 7db0f490..d1e61a5d 100644
--- a/stdlib/tomo.h
+++ b/stdlib/tomo.h
@@ -11,6 +11,7 @@
#include "arrays.h"
#include "bools.h"
+#include "bytes.h"
#include "c_strings.h"
#include "channels.h"
#include "datatypes.h"
diff --git a/test/bytes.tm b/test/bytes.tm
new file mode 100644
index 00000000..4db94930
--- /dev/null
+++ b/test/bytes.tm
@@ -0,0 +1,8 @@
+
+func main():
+ !! Test bytes:
+ >> 100[B]
+ = 100[B]
+
+ >> 0xFF[B]
+ = 255[B]
diff --git a/test/integers.tm b/test/integers.tm
index e15b9473..363fdfdd 100644
--- a/test/integers.tm
+++ b/test/integers.tm
@@ -11,8 +11,8 @@ func main():
>> 2 * 3 + 4
= 10
- >> 1i8 + 2i16
- = 3_i16
+ >> 1[8] + 2[16]
+ = 3[16]
>> 1 << 10
= 1024
@@ -32,7 +32,7 @@ func main():
>> nums
= "1,2,3,4,5,"
- >> x := 123i64
+ >> x := 123[64]
>> x:format(digits=5)
= "00123"
>> x:hex()
@@ -42,16 +42,16 @@ func main():
>> Int.random(1, 100)
>> Int64.min
- = -9223372036854775808_i64
+ = -9223372036854775808[64]
>> Int64.max
- = 9223372036854775807_i64
+ = 9223372036854775807[64]
- >> 123_i32:hex()
+ >> 123[32]:hex()
= "0x7B"
- >> 123_i16:hex()
+ >> 123[16]:hex()
= "0x7B"
- >> 123_i8:hex()
+ >> 123[8]:hex()
= "0x7B"
>> Int(2.1)
diff --git a/test/lang.tm b/test/lang.tm
index a637b442..e19c1e2a 100644
--- a/test/lang.tm
+++ b/test/lang.tm
@@ -33,7 +33,7 @@ func main():
>> $HTML"$(1 + 2)"
= $HTML"3"
- >> $HTML"$(3_i8)"
+ >> $HTML"$(3[8])"
= $HTML"3"
>> html:paragraph()
diff --git a/test/optionals.tm b/test/optionals.tm
index 928587f7..6d7db835 100644
--- a/test/optionals.tm
+++ b/test/optionals.tm
@@ -21,7 +21,7 @@ func maybe_int(should_i:Bool)->Int?:
func maybe_int64(should_i:Bool)->Int64?:
if should_i:
- return 123_i64
+ return 123[64]
else:
return !Int64
@@ -110,12 +110,12 @@ func main():
!! ...
!! Int64s:
>> yep := maybe_int64(yes)
- = 123_i64?
+ = 123[64]?
>> nope := maybe_int64(no)
= !Int64
>> if yep:
>> yep
- = 123_i64
+ = 123[64]
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
diff --git a/test/paths.tm b/test/paths.tm
index 8d5a92f2..996b6dde 100644
--- a/test/paths.tm
+++ b/test/paths.tm
@@ -24,6 +24,8 @@ func main():
>> tmpfile:append("!")
>> tmpfile:read()
= "Hello world!"
+ >> tmpfile:read_bytes()
+ = [72[B], 101[B], 108[B], 108[B], 111[B], 32[B], 119[B], 111[B], 114[B], 108[B], 100[B], 33[B]]
>> tmpdir:files():has(tmpfile)
= yes
diff --git a/test/text.tm b/test/text.tm
index 5de66a60..f27eaf37 100644
--- a/test/text.tm
+++ b/test/text.tm
@@ -29,17 +29,19 @@ func main():
>> amelie:split()
= ["A", "m", "é", "l", "i", "e"] : [Text]
>> amelie:utf32_codepoints()
- = [65_i32, 109_i32, 233_i32, 108_i32, 105_i32, 101_i32] : [Int32]
+ = [65[32], 109[32], 233[32], 108[32], 105[32], 101[32]] : [Int32]
>> amelie:utf8_bytes()
- = [65_i8, 109_i8, -61_i8, -87_i8, 108_i8, 105_i8, 101_i8] : [Int8]
+ = [65[B], 109[B], 195[B], 169[B], 108[B], 105[B], 101[B]] : [Byte]
+ >> Text.from_bytes([65[B], 109[B], 195[B], 169[B], 108[B], 105[B], 101[B]])
+ = "Amélie"
>> amelie2 := "Am$(\U65\U301)lie"
>> amelie2:split()
= ["A", "m", "é", "l", "i", "e"] : [Text]
>> amelie2:utf32_codepoints()
- = [65_i32, 109_i32, 233_i32, 108_i32, 105_i32, 101_i32] : [Int32]
+ = [65[32], 109[32], 233[32], 108[32], 105[32], 101[32]] : [Int32]
>> amelie2:utf8_bytes()
- = [65_i8, 109_i8, -61_i8, -87_i8, 108_i8, 105_i8, 101_i8] : [Int8]
+ = [65[B], 109[B], 195[B], 169[B], 108[B], 105[B], 101[B]] : [Byte]
>> amelie:codepoint_names()
= ["LATIN CAPITAL LETTER A", "LATIN SMALL LETTER M", "LATIN SMALL LETTER E WITH ACUTE", "LATIN SMALL LETTER L", "LATIN SMALL LETTER I", "LATIN SMALL LETTER E"]
@@ -193,11 +195,11 @@ func main():
>> " one two three ":find($/{id}/, start=5)
= 8
- >> len := 0_i64
+ >> len := 0[64]
>> " one ":find($/{id}/, length=&len)
= 4
>> len
- = 3_i64
+ = 3[64]
!! Test text slicing:
>> "abcdef":slice()
@@ -218,7 +220,7 @@ func main():
>> house:codepoint_names()
= ["CJK Unified Ideographs-5BB6"]
>> house:utf32_codepoints()
- = [23478_i32]
+ = [23478[32]]
>> "🐧":codepoint_names()
= ["PENGUIN"]
diff --git a/typecheck.c b/typecheck.c
index 86c204dc..5f40b078 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -503,6 +503,7 @@ type_t *get_type(env_t *env, ast_t *ast)
auto i = Match(ast, Int);
switch (i->bits) {
case IBITS_UNSPECIFIED: return Type(BigIntType);
+ case IBITS_BYTE: return Type(ByteType);
case IBITS8: return Type(IntType, .bits=TYPE_IBITS8);
case IBITS16: return Type(IntType, .bits=TYPE_IBITS16);
case IBITS32: return Type(IntType, .bits=TYPE_IBITS32);
@@ -1006,7 +1007,8 @@ type_t *get_type(env_t *env, ast_t *ast)
auto rhs_ptr = Match(rhs_t, PointerType);
if (type_eq(lhs_ptr->pointed, rhs_ptr->pointed))
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
- } else if (is_int_type(lhs_t) && is_int_type(rhs_t)) {
+ } else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
+ || (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
return get_math_type(env, ast, lhs_t, rhs_t);
}
code_err(ast, "I can't figure out the type of this `and` expression because the left side is a %T, but the right side is a %T",
@@ -1017,7 +1019,8 @@ type_t *get_type(env_t *env, ast_t *ast)
return lhs_t;
} else if (lhs_t->tag == BoolType && (rhs_t->tag == AbortType || rhs_t->tag == ReturnType)) {
return lhs_t;
- } else if (is_int_type(lhs_t) && is_int_type(rhs_t)) {
+ } else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
+ || (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
return get_math_type(env, ast, lhs_t, rhs_t);
} else if (lhs_t->tag == OptionalType) {
if (can_promote(rhs_t, lhs_t))
@@ -1038,7 +1041,8 @@ type_t *get_type(env_t *env, ast_t *ast)
case BINOP_XOR: {
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
return lhs_t;
- } else if (is_int_type(lhs_t) && is_int_type(rhs_t)) {
+ } else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
+ || (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
return get_math_type(env, ast, lhs_t, rhs_t);
}
diff --git a/types.c b/types.c
index bc54015c..b1269bbb 100644
--- a/types.c
+++ b/types.c
@@ -23,6 +23,7 @@ CORD type_to_cord(type_t *t) {
case VoidType: return "Void";
case MemoryType: return "Memory";
case BoolType: return "Bool";
+ case ByteType: return "Byte";
case CStringType: return "CString";
case TextType: return Match(t, TextType)->lang ? Match(t, TextType)->lang : "Text";
case BigIntType: return "Int";
@@ -165,6 +166,7 @@ static PUREFUNC inline double type_min_magnitude(type_t *t)
{
switch (t->tag) {
case BoolType: return (double)false;
+ case ByteType: return 0;
case BigIntType: return -1./0.;
case IntType: {
switch (Match(t, IntType)->bits) {
@@ -184,6 +186,7 @@ static PUREFUNC inline double type_max_magnitude(type_t *t)
{
switch (t->tag) {
case BoolType: return (double)true;
+ case ByteType: return (double)UINT8_MAX;
case BigIntType: return 1./0.;
case IntType: {
switch (Match(t, IntType)->bits) {
@@ -396,7 +399,7 @@ PUREFUNC bool is_numeric_type(type_t *t)
PUREFUNC bool supports_optionals(type_t *t)
{
switch (t->tag) {
- case BoolType: case CStringType: case BigIntType: case NumType: case TextType:
+ case BoolType: case ByteType: case CStringType: case BigIntType: case NumType: case TextType:
case ArrayType: case SetType: case TableType: case FunctionType: case ClosureType:
case PointerType: case IntType:
return true;
@@ -411,6 +414,7 @@ PUREFUNC size_t type_size(type_t *t)
case UnknownType: case AbortType: case ReturnType: case VoidType: return 0;
case MemoryType: errx(1, "Memory has undefined type size");
case BoolType: return sizeof(bool);
+ case ByteType: return sizeof(uint8_t);
case CStringType: return sizeof(char*);
case BigIntType: return sizeof(Int_t);
case IntType: {
@@ -489,6 +493,7 @@ PUREFUNC size_t type_align(type_t *t)
case UnknownType: case AbortType: case ReturnType: case VoidType: return 0;
case MemoryType: errx(1, "Memory has undefined type alignment");
case BoolType: return __alignof__(bool);
+ case ByteType: return __alignof__(uint8_t);
case CStringType: return __alignof__(char*);
case BigIntType: return __alignof__(Int_t);
case IntType: {
diff --git a/types.h b/types.h
index 8b1ded4a..46469c65 100644
--- a/types.h
+++ b/types.h
@@ -43,6 +43,7 @@ struct type_s {
VoidType,
MemoryType,
BoolType,
+ ByteType,
BigIntType,
IntType,
NumType,
@@ -72,6 +73,7 @@ struct type_s {
struct {
enum { TYPE_IBITS8=8, TYPE_IBITS16=16, TYPE_IBITS32=32, TYPE_IBITS64=64 } bits;
} IntType;
+ struct {} ByteType;
struct {
enum { TYPE_NBITS32=32, TYPE_NBITS64=64 } bits;
} NumType;