diff --git a/Makefile b/Makefile index d4694d5..3b21eba 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ O=-Og CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m\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 55ac28f..54a96d3 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 2802519..a1f4d64 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 763f863..642293c 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 50ecd03..1b25bcf 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 a3c9e01..85e5324 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 de48316..6408cc8 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 18960b0..0b2b570 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 c0c8538..e078146 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 9590215..0a27ece 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 a5ad136..34c021c 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 0000000..5b471bf --- /dev/null +++ b/stdlib/bytes.c @@ -0,0 +1,38 @@ +// The logic for unsigned bytes +#include +#include + +#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 0000000..d74ab80 --- /dev/null +++ b/stdlib/bytes.h @@ -0,0 +1,29 @@ +#pragma once + +// An unsigned byte datatype + +#include +#include + +#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 ef58898..6caac31 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 231a7c2..bebb938 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 e0d8525..d06d4fd 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 7db0f49..d1e61a5 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 0000000..4db9493 --- /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 e15b947..363fdfd 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 a637b44..e19c1e2 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 928587f..6d7db83 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 8d5a92f..996b6dd 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 5de66a6..f27eaf3 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 86c204d..5f40b07 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 bc54015..b1269bb 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 8b1ded4..46469c6 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;