diff --git a/doc/langref.md b/doc/langref.md index 6c2789367b..134cf8ac65 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -370,10 +370,10 @@ TODO ## Built-in Functions -Built-in functions are prefixed with `@`. Remember that the `inline` keyword on +Built-in functions are prefixed with `@`. Remember that the `comptime` keyword on a parameter means that the parameter must be known at compile time. -### @alloca(inline T: type, count: usize) -> []T +### @alloca(comptime T: type, count: usize) -> []T Allocates memory in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca returns to its caller, @@ -391,13 +391,13 @@ The allocated memory contents are undefined. This function returns a compile-time constant, which is the type of the expression passed as an argument. The expression is *not evaluated*. -### @sizeOf(inline T: type) -> (number literal) +### @sizeOf(comptime T: type) -> (number literal) This function returns the number of bytes it takes to store T in memory. The result is a target-specific compile time constant. -### @alignOf(inline T: type) -> (number literal) +### @alignOf(comptime T: type) -> (number literal) This function returns the number of bytes that this type should be aligned to for the current target. @@ -414,10 +414,10 @@ false otherwise. ``` Function Operation -@addWithOverflow(inline T: type, a: T, b: T, result: &T) -> bool *x = a + b -@subWithOverflow(inline T: type, a: T, b: T, result: &T) -> bool *x = a - b -@mulWithOverflow(inline T: type, a: T, b: T, result: &T) -> bool *x = a * b -@shlWithOverflow(inline T: type, a: T, b: T, result: &T) -> bool *x = a << b +@addWithOverflow(comptime T: type, a: T, b: T, result: &T) -> bool *x = a + b +@subWithOverflow(comptime T: type, a: T, b: T, result: &T) -> bool *x = a - b +@mulWithOverflow(comptime T: type, a: T, b: T, result: &T) -> bool *x = a * b +@shlWithOverflow(comptime T: type, a: T, b: T, result: &T) -> bool *x = a << b ``` ### @memset(dest: &u8, c: u8, byte_count: usize) @@ -475,27 +475,27 @@ aggressive optimizations. This function is only valid within function scope. -### @maxValue(inline T: type) -> (number literal) +### @maxValue(comptime T: type) -> (number literal) This function returns the maximum integer value of the integer type T. The result is a compile time constant. For some types such as `c_long`, the result is marked as depending on a compile variable. -### @minValue(inline T: type) -> (number literal) +### @minValue(comptime T: type) -> (number literal) This function returns the minimum integer value of the integer type T. The result is a compile time constant. For some types such as `c_long`, the result is marked as depending on a compile variable. -### @memberCount(inline T: type) -> (number literal) +### @memberCount(comptime T: type) -> (number literal) This function returns the number of enum values in an enum type. The result is a compile time constant. -### @import(inline path: []u8) -> (namespace) +### @import(comptime path: []u8) -> (namespace) This function finds a zig file corresponding to `path` and imports all the public top level declarations into the resulting namespace. @@ -516,25 +516,25 @@ appending to a temporary buffer which is then parsed as C code. This function is only valid at top level scope. -### @cInclude(inline path: []u8) +### @cInclude(comptime path: []u8) This function can only occur inside `@c_import`. This appends `#include <$path>\n` to the `c_import` temporary buffer. -### @cDefine(inline name: []u8, value) +### @cDefine(comptime name: []u8, value) This function can only occur inside `@c_import`. This appends `#define $name $value` to the `c_import` temporary buffer. -### @cUndef(inline name: []u8) +### @cUndef(comptime name: []u8) This function can only occur inside `@c_import`. This appends `#undef $name` to the `c_import` temporary buffer. -### @compileVar(inline name: []u8) -> (varying type) +### @compileVar(comptime name: []u8) -> (varying type) This function returns a compile-time variable. There are built in compile variables: @@ -584,10 +584,14 @@ error OutOfMem; Then the string representation is "OutOfMem". -If there are no calls to `@err_name` in an entire application, then no error +If there are no calls to `@errorName` in an entire application, then no error name table will be generated. -### @embedFile(inline path: []u8) -> [X]u8 +### @typeName(T: type) -> []u8 + +This function returns the string representation of a type. + +### @embedFile(comptime path: []u8) -> [X]u8 This function returns a compile time constant fixed-size array with length equal to the byte count of the file given by `path`. The contents of the array @@ -610,7 +614,7 @@ The caller guarantees that this operation will have no remainder. In debug mode, a remainder causes a panic. In release mode, a remainder is undefined behavior. -### @truncate(inline T: type, integer) -> T +### @truncate(comptime T: type, integer) -> T This function truncates bits from an integer type, resulting in a smaller integer type. @@ -631,14 +635,14 @@ const b: u8 = @truncate(u8, a); // b is now 0xcd ``` -### @compileError(inline msg: []u8) +### @compileError(comptime msg: []u8) This function, when semantically analyzed, causes a compile error with the message `msg`. There are several ways that code avoids being semantically checked, such as using `if` -or `switch` with compile time constants, and inline functions. +or `switch` with compile time constants, and comptime functions. -### @intType(inline is_signed: bool, inline bit_count: u8) -> type +### @intType(comptime is_signed: bool, comptime bit_count: u8) -> type This function returns an integer type with the given signness and bit count. @@ -646,3 +650,14 @@ This function returns an integer type with the given signness and bit count. Makes the target function a test function. +### @isInteger(comptime T: type) -> bool + +Returns whether a given type is an integer. + +### @isFloat(comptime T: type) -> bool + +Returns whether a given type is a float. + +### @canImplicitCast(comptime T: type, value) -> bool + +Returns whether a value can be implicitly casted to a given type. diff --git a/example/cat/main.zig b/example/cat/main.zig index 7aaefd8bf7..1c6a2ca8ae 100644 --- a/example/cat/main.zig +++ b/example/cat/main.zig @@ -2,8 +2,6 @@ const std = @import("std"); const io = std.io; const str = std.str; -// TODO var args printing - pub fn main(args: [][]u8) -> %void { const exe = args[0]; var catted_anything = false; diff --git a/src/all_types.hpp b/src/all_types.hpp index c5b78fe2b5..4579db7658 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -978,6 +978,9 @@ struct TypeTableEntry { HashMap arrays_by_size; TypeTableEntry *maybe_parent; TypeTableEntry *error_parent; + // If we generate a constant name value for this type, we memoize it here. + // The type of this is array + ConstExprValue *cached_const_name_val; }; struct PackageTableEntry { @@ -1086,6 +1089,10 @@ enum BuiltinFnId { BuiltinFnIdSetFnVisible, BuiltinFnIdSetDebugSafety, BuiltinFnIdAlloca, + BuiltinFnIdTypeName, + BuiltinFnIdIsInteger, + BuiltinFnIdIsFloat, + BuiltinFnIdCanImplicitCast, }; struct BuiltinFnEntry { @@ -1505,6 +1512,9 @@ enum IrInstructionId { IrInstructionIdPtrToInt, IrInstructionIdIntToEnum, IrInstructionIdCheckSwitchProngs, + IrInstructionIdTestType, + IrInstructionIdTypeName, + IrInstructionIdCanImplicitCast, }; struct IrInstruction { @@ -2188,6 +2198,26 @@ struct IrInstructionCheckSwitchProngs { size_t range_count; }; +struct IrInstructionTestType { + IrInstruction base; + + IrInstruction *type_value; + TypeTableEntryId type_id; +}; + +struct IrInstructionTypeName { + IrInstruction base; + + IrInstruction *type_value; +}; + +struct IrInstructionCanImplicitCast { + IrInstruction base; + + IrInstruction *type_value; + IrInstruction *target_value; +}; + enum LValPurpose { LValPurposeNone, LValPurposeAssign, diff --git a/src/codegen.cpp b/src/codegen.cpp index 29508a326e..9595fdefe9 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2279,6 +2279,9 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdTestComptime: case IrInstructionIdGeneratedCode: case IrInstructionIdCheckSwitchProngs: + case IrInstructionIdTestType: + case IrInstructionIdTypeName: + case IrInstructionIdCanImplicitCast: zig_unreachable(); case IrInstructionIdReturn: return ir_render_return(g, executable, (IrInstructionReturn *)instruction); @@ -3652,6 +3655,10 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdImport, "import", 1); create_builtin_fn(g, BuiltinFnIdCImport, "cImport", 1); create_builtin_fn(g, BuiltinFnIdErrName, "errorName", 1); + create_builtin_fn(g, BuiltinFnIdTypeName, "typeName", 1); + create_builtin_fn(g, BuiltinFnIdIsInteger, "isInteger", 1); + create_builtin_fn(g, BuiltinFnIdIsFloat, "isFloat", 1); + create_builtin_fn(g, BuiltinFnIdCanImplicitCast, "canImplicitCast", 2); create_builtin_fn(g, BuiltinFnIdEmbedFile, "embedFile", 1); create_builtin_fn(g, BuiltinFnIdCmpExchange, "cmpxchg", 5); create_builtin_fn(g, BuiltinFnIdFence, "fence", 1); diff --git a/src/ir.cpp b/src/ir.cpp index 588859ea8e..7d8fe6539c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -495,6 +495,18 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckSwitchProng return IrInstructionIdCheckSwitchProngs; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionTestType *) { + return IrInstructionIdTestType; +} + +static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeName *) { + return IrInstructionIdTypeName; +} + +static constexpr IrInstructionId ir_instruction_id(IrInstructionCanImplicitCast *) { + return IrInstructionIdCanImplicitCast; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -2001,6 +2013,45 @@ static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, return &instruction->base; } +static IrInstruction *ir_build_test_type(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value, TypeTableEntryId type_id) +{ + IrInstructionTestType *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->type_value = type_value; + instruction->type_id = type_id; + + ir_ref_instruction(type_value, irb->current_basic_block); + + return &instruction->base; +} + +static IrInstruction *ir_build_type_name(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value) +{ + IrInstructionTypeName *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->type_value = type_value; + + ir_ref_instruction(type_value, irb->current_basic_block); + + return &instruction->base; +} + +static IrInstruction *ir_build_can_implicit_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value, IrInstruction *target_value) +{ + IrInstructionCanImplicitCast *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->type_value = type_value; + instruction->target_value = target_value; + + ir_ref_instruction(type_value, irb->current_basic_block); + ir_ref_instruction(target_value, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) { return nullptr; } @@ -2605,6 +2656,28 @@ static IrInstruction *ir_instruction_checkswitchprongs_get_dep(IrInstructionChec return nullptr; } +static IrInstruction *ir_instruction_testtype_get_dep(IrInstructionTestType *instruction, size_t index) { + switch (index) { + case 0: return instruction->type_value; + default: return nullptr; + } +} + +static IrInstruction *ir_instruction_typename_get_dep(IrInstructionTypeName *instruction, size_t index) { + switch (index) { + case 0: return instruction->type_value; + default: return nullptr; + } +} + +static IrInstruction *ir_instruction_canimplicitcast_get_dep(IrInstructionCanImplicitCast *instruction, size_t index) { + switch (index) { + case 0: return instruction->type_value; + case 1: return instruction->target_value; + default: return nullptr; + } +} + static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) { switch (instruction->id) { case IrInstructionIdInvalid: @@ -2777,6 +2850,12 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_inttoenum_get_dep((IrInstructionIntToEnum *) instruction, index); case IrInstructionIdCheckSwitchProngs: return ir_instruction_checkswitchprongs_get_dep((IrInstructionCheckSwitchProngs *) instruction, index); + case IrInstructionIdTestType: + return ir_instruction_testtype_get_dep((IrInstructionTestType *) instruction, index); + case IrInstructionIdTypeName: + return ir_instruction_typename_get_dep((IrInstructionTypeName *) instruction, index); + case IrInstructionIdCanImplicitCast: + return ir_instruction_canimplicitcast_get_dep((IrInstructionCanImplicitCast *) instruction, index); } zig_unreachable(); } @@ -3594,6 +3673,18 @@ static IrInstruction *ir_gen_overflow_op(IrBuilder *irb, Scope *scope, AstNode * return ir_build_overflow_op(irb, scope, node, op, type_value, op1, op2, result_ptr, nullptr); } +static IrInstruction *ir_gen_test_type(IrBuilder *irb, Scope *scope, AstNode *node, TypeTableEntryId type_id) { + assert(node->type == NodeTypeFnCallExpr); + + AstNode *type_node = node->data.fn_call_expr.params.at(0); + + IrInstruction *type_value = ir_gen_node(irb, type_node, scope); + if (type_value == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + return ir_build_test_type(irb, scope, node, type_value, type_id); +} + static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNode *node) { assert(node->type == NodeTypeFnCallExpr); @@ -3995,6 +4086,33 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_gen_overflow_op(irb, scope, node, IrOverflowOpMul); case BuiltinFnIdShlWithOverflow: return ir_gen_overflow_op(irb, scope, node, IrOverflowOpShl); + case BuiltinFnIdTypeName: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + return ir_build_type_name(irb, scope, node, arg0_value); + } + case BuiltinFnIdIsInteger: + return ir_gen_test_type(irb, scope, node, TypeTableEntryIdInt); + case BuiltinFnIdIsFloat: + return ir_gen_test_type(irb, scope, node, TypeTableEntryIdFloat); + case BuiltinFnIdCanImplicitCast: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + return ir_build_can_implicit_cast(irb, scope, node, arg0_value, arg1_value); + } } zig_unreachable(); } @@ -8942,7 +9060,7 @@ static TypeTableEntry *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructi case TypeTableEntryIdEnumTag: case TypeTableEntryIdArgTuple: { - ConstExprValue *out_val = ir_build_const_from(ira, &typeof_instruction->base, false); + ConstExprValue *out_val = ir_build_const_from(ira, &typeof_instruction->base, true); // TODO depends_on_compile_var should be set based on whether the type of the expression // depends_on_compile_var. but we currently don't have a thing to tell us if the type of // something depends on a compile var @@ -9622,6 +9740,8 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira, pointee_val = nullptr; } TypeTableEntry *canon_target_type = get_underlying_type(target_type); + ensure_complete_type(ira->codegen, target_type); + switch (canon_target_type->id) { case TypeTableEntryIdInvalid: case TypeTableEntryIdVar: @@ -10260,6 +10380,24 @@ static TypeTableEntry *ir_analyze_instruction_err_name(IrAnalyze *ira, IrInstruc return str_type; } +static TypeTableEntry *ir_analyze_instruction_type_name(IrAnalyze *ira, IrInstructionTypeName *instruction) { + IrInstruction *type_value = instruction->type_value->other; + TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); + if (type_entry->id == TypeTableEntryIdInvalid) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *str_type = get_slice_type(ira->codegen, ira->codegen->builtin_types.entry_u8, true); + if (!type_entry->cached_const_name_val) { + ConstExprValue *array_val = create_const_str_lit(ira->codegen, &type_entry->name); + type_entry->cached_const_name_val = create_const_slice(ira->codegen, + array_val, 0, buf_len(&type_entry->name), true); + } + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base, + type_value->value.depends_on_compile_var); + *out_val = *type_entry->cached_const_name_val; + return str_type; +} + static TypeTableEntry *ir_analyze_instruction_c_import(IrAnalyze *ira, IrInstructionCImport *instruction) { AstNode *node = instruction->base.source_node; assert(node->type == NodeTypeFnCallExpr); @@ -11370,6 +11508,44 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira return ira->codegen->builtin_types.entry_void; } +static TypeTableEntry *ir_analyze_instruction_test_type(IrAnalyze *ira, IrInstructionTestType *instruction) { + IrInstruction *type_value = instruction->type_value->other; + TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); + if (type_entry->id == TypeTableEntryIdInvalid) + return ira->codegen->builtin_types.entry_invalid; + + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base, type_value->value.depends_on_compile_var); + out_val->data.x_bool = (type_entry->id == instruction->type_id); + return ira->codegen->builtin_types.entry_bool; +} + +static TypeTableEntry *ir_analyze_instruction_can_implicit_cast(IrAnalyze *ira, + IrInstructionCanImplicitCast *instruction) +{ + IrInstruction *type_value = instruction->type_value->other; + TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); + if (type_entry->id == TypeTableEntryIdInvalid) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *target_value = instruction->target_value->other; + if (target_value->value.type->id == TypeTableEntryIdInvalid) + return ira->codegen->builtin_types.entry_invalid; + + ImplicitCastMatchResult result = ir_types_match_with_implicit_cast(ira, type_entry, target_value->value.type, + target_value); + + if (result == ImplicitCastMatchResultReportedError) { + zig_panic("TODO refactor implicit cast tester to return bool without reporting errors"); + } + + // TODO in order to known depends_on_compile_var we have to known if the type of the target + // depends on a compile var + bool depends_on_compile_var = true; + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base, depends_on_compile_var); + out_val->data.x_bool = (result == ImplicitCastMatchResultYes); + return ira->codegen->builtin_types.entry_bool; +} + static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) { switch (instruction->id) { case IrInstructionIdInvalid: @@ -11471,6 +11647,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_compile_err(ira, (IrInstructionCompileErr *)instruction); case IrInstructionIdErrName: return ir_analyze_instruction_err_name(ira, (IrInstructionErrName *)instruction); + case IrInstructionIdTypeName: + return ir_analyze_instruction_type_name(ira, (IrInstructionTypeName *)instruction); case IrInstructionIdCImport: return ir_analyze_instruction_c_import(ira, (IrInstructionCImport *)instruction); case IrInstructionIdCInclude: @@ -11525,6 +11703,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_test_comptime(ira, (IrInstructionTestComptime *)instruction); case IrInstructionIdCheckSwitchProngs: return ir_analyze_instruction_check_switch_prongs(ira, (IrInstructionCheckSwitchProngs *)instruction); + case IrInstructionIdTestType: + return ir_analyze_instruction_test_type(ira, (IrInstructionTestType *)instruction); + case IrInstructionIdCanImplicitCast: + return ir_analyze_instruction_can_implicit_cast(ira, (IrInstructionCanImplicitCast *)instruction); case IrInstructionIdMaybeWrap: case IrInstructionIdErrWrapCode: case IrInstructionIdErrWrapPayload: @@ -11691,6 +11873,9 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdPtrToInt: case IrInstructionIdIntToPtr: case IrInstructionIdIntToEnum: + case IrInstructionIdTestType: + case IrInstructionIdTypeName: + case IrInstructionIdCanImplicitCast: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 418b4b5e7c..79e0bdb895 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -807,6 +807,26 @@ static void ir_print_check_switch_prongs(IrPrint *irp, IrInstructionCheckSwitchP fprintf(irp->f, ")"); } +static void ir_print_test_type(IrPrint *irp, IrInstructionTestType *instruction) { + fprintf(irp->f, "@testType("); + ir_print_other_instruction(irp, instruction->type_value); + fprintf(irp->f, ")"); +} + +static void ir_print_type_name(IrPrint *irp, IrInstructionTypeName *instruction) { + fprintf(irp->f, "@typeName("); + ir_print_other_instruction(irp, instruction->type_value); + fprintf(irp->f, ")"); +} + +static void ir_print_can_implicit_cast(IrPrint *irp, IrInstructionCanImplicitCast *instruction) { + fprintf(irp->f, "@canImplicitCast("); + ir_print_other_instruction(irp, instruction->type_value); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->target_value); + fprintf(irp->f, ")"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); switch (instruction->id) { @@ -1064,6 +1084,15 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdCheckSwitchProngs: ir_print_check_switch_prongs(irp, (IrInstructionCheckSwitchProngs *)instruction); break; + case IrInstructionIdTestType: + ir_print_test_type(irp, (IrInstructionTestType *)instruction); + break; + case IrInstructionIdTypeName: + ir_print_type_name(irp, (IrInstructionTypeName *)instruction); + break; + case IrInstructionIdCanImplicitCast: + ir_print_can_implicit_cast(irp, (IrInstructionCanImplicitCast *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/std/debug.zig b/std/debug.zig index 4051ff123a..15aebf3a23 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -50,10 +50,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream) -> %void { const compile_unit = findCompileUnit(st, return_address) ?? return error.MissingDebugInfo; const name = %return compile_unit.die.getAttrString(st, DW.AT_name); - %return out_stream.printInt(usize, return_address); - %return out_stream.printf(" -> "); - %return out_stream.printf(name); - %return out_stream.printf("\n"); + %return out_stream.printf("{} -> {}\n", return_address, name); maybe_fp = *(&const ?&const u8)(fp); } }, diff --git a/std/io.zig b/std/io.zig index 752f01c3de..9da4c17b16 100644 --- a/std/io.zig +++ b/std/io.zig @@ -80,7 +80,7 @@ pub const OutStream = struct { self.index += 1; } - pub fn write(self: &OutStream, bytes: []const u8) -> %usize { + pub fn write(self: &OutStream, bytes: []const u8) -> %void { var src_bytes_left = bytes.len; var src_index: usize = 0; const dest_space_left = self.buffer.len - self.index; @@ -94,25 +94,89 @@ pub const OutStream = struct { } src_bytes_left -= copy_amt; } - return bytes.len; } - /// Prints a byte buffer, flushes the buffer, then returns the number of - /// bytes printed. The "f" is for "flush". - pub fn printf(self: &OutStream, str: []const u8) -> %usize { - const byte_count = %return self.write(str); + const State = enum { // TODO put inside printf function and make sure the name and debug info is correct + Start, + OpenBrace, + CloseBrace, + }; + + /// Calls print and then flushes the buffer. + pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) -> %void { + comptime var start_index: usize = 0; + comptime var state = State.Start; + comptime var next_arg: usize = 0; + inline for (format) |c, i| { + switch (state) { + State.Start => switch (c) { + '{' => { + if (start_index < i) %return self.write(format[start_index...i]); + state = State.OpenBrace; + }, + '}' => { + if (start_index < i) %return self.write(format[start_index...i]); + state = State.CloseBrace; + }, + else => {}, + }, + State.OpenBrace => switch (c) { + '{' => { + state = State.Start; + start_index = i; + }, + '}' => { + %return self.printValue(args[next_arg]); + next_arg += 1; + state = State.Start; + start_index = i + 1; + }, + else => @compileError("Unknown format character: " ++ c), + }, + State.CloseBrace => switch (c) { + '}' => { + state = State.Start; + start_index = i; + }, + else => @compileError("Single '}' encountered in format string"), + }, + } + } + comptime { + if (args.len != next_arg) { + @compileError("Unused arguments"); + } + if (state != State.Start) { + @compileError("Incomplete format string: " ++ format); + } + } + inline if (start_index < format.len) { + %return self.write(format[start_index...format.len]); + } %return self.flush(); - return byte_count; } - pub fn printInt(self: &OutStream, comptime T: type, x: T) -> %usize { + pub fn printValue(self: &OutStream, value: var) -> %void { + const T = @typeOf(value); + if (@isInteger(T)) { + return self.printInt(T, value); + } else if (@isFloat(T)) { + return self.printFloat(T, value); + } else if (@canImplicitCast([]const u8, value)) { + const casted_value = ([]const u8)(value); + return self.write(casted_value); + } else { + @compileError("Unable to print type '" ++ @typeName(T) ++ "'"); + } + } + + pub fn printInt(self: &OutStream, comptime T: type, x: T) -> %void { // TODO replace max_u64_base10_digits with math.log10(math.pow(2, @sizeOf(T))) if (self.index + max_u64_base10_digits >= self.buffer.len) { %return self.flush(); } const amt_printed = bufPrintInt(T, self.buffer[self.index...], x); self.index += amt_printed; - return amt_printed; } pub fn flush(self: &OutStream) -> %void { diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 64482cf427..8d49dc3241 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -517,3 +517,52 @@ fn testArray2DConstDoublePtr(ptr: &const f32) { assert(ptr[0] == 1.0); assert(ptr[1] == 2.0); } + +fn isInteger() { + @setFnTest(this); + + comptime { + assert(@isInteger(i8)); + assert(@isInteger(u8)); + assert(@isInteger(i64)); + assert(@isInteger(u64)); + assert(!@isInteger(f32)); + assert(!@isInteger(f64)); + assert(!@isInteger(bool)); + assert(!@isInteger(&i32)); + } +} + +fn isFloat() { + @setFnTest(this); + + comptime { + assert(!@isFloat(i8)); + assert(!@isFloat(u8)); + assert(!@isFloat(i64)); + assert(!@isFloat(u64)); + assert(@isFloat(f32)); + assert(@isFloat(f64)); + assert(!@isFloat(bool)); + assert(!@isFloat(&f32)); + } +} + +fn canImplicitCast() { + @setFnTest(this); + + comptime { + assert(@canImplicitCast(i64, i32(3))); + assert(!@canImplicitCast(i32, f32(1.234))); + assert(@canImplicitCast([]const u8, "aoeu")); + } +} + +fn typeName() { + @setFnTest(this); + + comptime { + assert(str.eql(@typeName(i64), "i64")); + assert(str.eql(@typeName(&usize), "&usize")); + } +}