diff --git a/src/ast.cpp b/src/ast.cpp index 5a2addb..bdd69a7 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -14,11 +14,28 @@ JamBasicBlockRef CurrentLoopContinue = nullptr; JamBasicBlockRef CurrentLoopBreak = nullptr; +// Forward declaration: defined later in this file. Used by CallExprAST to +// coerce each argument to the callee's parameter type before emitting the +// call (prevents bogus IR like `call i32 @abs(i8 -7)`). +static JamValueRef coerceTo(JamCodegenContext &ctx, JamValueRef val, + JamTypeRef expected); + JamValueRef NumberExprAST::codegen(JamCodegenContext &ctx) { - // Choose appropriate type based on value range + // If a use site fixed our type, materialize the constant at that type + // directly. This avoids downstream sext/zext ambiguity on the resulting + // Value when the literal is consumed by a wider/narrower context. + if (ExpectedType && JamLLVMTypeIsInteger(ExpectedType)) { + if (IsNegative) { + int64_t signedVal = -static_cast(Val); + return JamLLVMConstInt(ExpectedType, + static_cast(signedVal), true); + } + return JamLLVMConstInt(ExpectedType, Val, false); + } + + // Fallback: smallest integer type that fits. JamTypeRef IntType; if (IsNegative) { - // For negative values, use signed ranges if (Val <= 128) { IntType = ctx.getInt8Type(); } else if (Val <= 32768) { @@ -28,11 +45,9 @@ JamValueRef NumberExprAST::codegen(JamCodegenContext &ctx) { } else { IntType = ctx.getInt64Type(); } - // Create two's complement representation int64_t signedVal = -static_cast(Val); return JamLLVMConstInt(IntType, static_cast(signedVal), true); } else { - // For positive values, use unsigned ranges if (Val <= 255) { IntType = ctx.getInt8Type(); } else if (Val <= 65535) { @@ -50,6 +65,11 @@ JamValueRef BooleanExprAST::codegen(JamCodegenContext &ctx) { return JamLLVMConstInt(ctx.getInt1Type(), Val ? 1 : 0, false); } +JamValueRef UndefinedExprAST::codegen(JamCodegenContext &) { + throw std::runtime_error( + "`undefined` is only valid as a `var` declaration initializer"); +} + JamValueRef UnaryExprAST::codegen(JamCodegenContext &ctx) { JamValueRef operandVal = Operand->codegen(ctx); if (!operandVal) return nullptr; @@ -220,8 +240,19 @@ JamValueRef CallExprAST::codegen(JamCodegenContext &ctx) { std::vector ArgsV; for (unsigned i = 0, e = Args.size(); i != e; ++i) { - ArgsV.push_back(Args[i]->codegen(ctx)); - if (!ArgsV.back()) return nullptr; + JamTypeRef expected = JamLLVMTypeOf(JamLLVMGetParam(CalleeF, i)); + // Integer literals adapt to the callee's expected type so the + // constant is materialized at the right width and signedness from + // the start, avoiding sext/zext guesswork after the fact. + if (auto *numExpr = dynamic_cast(Args[i].get())) { + numExpr->setExpectedType(expected); + } + JamValueRef argVal = Args[i]->codegen(ctx); + if (!argVal) return nullptr; + // Non-literal args (variables, expressions) still need coerceTo as a + // safety net for type-width drift. + argVal = coerceTo(ctx, argVal, expected); + ArgsV.push_back(argVal); } return JamLLVMBuildCall(ctx.getBuilder(), CalleeF, ArgsV.data(), @@ -339,11 +370,22 @@ JamValueRef CallExprAST::generateAssertCall(JamCodegenContext &ctx) { } JamValueRef actual = Args[0]->codegen(ctx); + if (!actual) return nullptr; + JamTypeRef actualType = JamLLVMTypeOf(actual); + + // If the expected arg is an integer literal, materialize it directly at + // the actual's type so we never have to guess sext vs zext later. + if (auto *numExpr = dynamic_cast(Args[1].get())) { + if (JamLLVMTypeIsInteger(actualType)) { + numExpr->setExpectedType(actualType); + } + } JamValueRef expected = Args[1]->codegen(ctx); - if (!actual || !expected) return nullptr; + if (!expected) return nullptr; - // Ensure both values have the same type - JamTypeRef actualType = JamLLVMTypeOf(actual); + // Fallback widening for non-literal expected (variables, expressions). + // We don't track signedness on values, so this is best-effort and only + // safe for non-negative ranges. JamTypeRef expectedType = JamLLVMTypeOf(expected); if (actualType != expectedType) { if (JamLLVMTypeIsInteger(actualType) && @@ -424,6 +466,95 @@ JamValueRef ReturnExprAST::codegen(JamCodegenContext &ctx) { return RetVal; } +// Walks an indexable lvalue expression (the Object inside an IndexExprAST) +// and returns a pointer to the indexed array element, plus the element type +// via the out-parameter. Supports: +// - Plain locals: var arr: [N]T; arr[i] +// - Struct field chain: var g: Game; ... g.board[i], g.inner.cells[i] +// +// For struct chains we GEP through each field rather than load-then-extract, +// which is essential for arrays-in-structs (loading a 200-byte board to read +// one byte would be terrible). +static JamValueRef +resolveIndexedElementPtr(JamCodegenContext &ctx, ExprAST *object, + JamValueRef idxVal, JamTypeRef &outElemType) { + if (auto *varExpr = dynamic_cast(object)) { + const std::string &name = varExpr->getName(); + JamValueRef alloca = ctx.getVariable(name); + if (!alloca) { + throw std::runtime_error("Unknown variable: " + name); + } + JamTypeRef arrayType = JamLLVMGetAllocatedType(alloca); + outElemType = JamLLVMGetArrayElementType(arrayType); + return JamLLVMBuildArrayGEP(ctx.getBuilder(), arrayType, alloca, + idxVal, "idxgep"); + } + + if (auto *memberExpr = dynamic_cast(object)) { + // Collect the field chain from outer-most member back to the leaf + // variable. memberExpr->getMember() is the innermost field. + std::vector chain; + chain.push_back(memberExpr->getMember()); + ExprAST *cur = memberExpr->getObject(); + while (auto *ma = dynamic_cast(cur)) { + chain.push_back(ma->getMember()); + cur = ma->getObject(); + } + auto *leafVar = dynamic_cast(cur); + if (!leafVar) { + throw std::runtime_error( + "Indexing into a non-variable lvalue is not supported"); + } + const std::string &varName = leafVar->getName(); + JamValueRef alloca = ctx.getVariable(varName); + if (!alloca) { + throw std::runtime_error("Unknown variable: " + varName); + } + std::string typeAtLevel = ctx.getVariableType(varName); + if (typeAtLevel.length() >= 6 && + typeAtLevel.substr(0, 6) == "const ") { + typeAtLevel = typeAtLevel.substr(6); + } + + // Reverse so path[0] is the outermost field on the leaf variable. + std::vector path(chain.rbegin(), chain.rend()); + + JamValueRef currentPtr = alloca; + JamTypeRef currentType = ctx.getTypeFromString(typeAtLevel); + for (size_t i = 0; i < path.size(); i++) { + const auto *info = ctx.getStruct(typeAtLevel); + if (!info) { + throw std::runtime_error( + "Cannot index through field '" + path[i] + + "' on non-struct: " + typeAtLevel); + } + int idx = ctx.getFieldIndex(typeAtLevel, path[i]); + if (idx < 0) { + throw std::runtime_error("Unknown field '" + path[i] + + "' in struct " + typeAtLevel); + } + currentPtr = JamLLVMBuildStructGEP( + ctx.getBuilder(), currentType, currentPtr, + static_cast(idx), path[i].c_str()); + std::string fieldType = info->fields[idx].second; + if (fieldType.length() >= 6 && + fieldType.substr(0, 6) == "const ") { + fieldType = fieldType.substr(6); + } + typeAtLevel = fieldType; + currentType = ctx.getTypeFromString(typeAtLevel); + } + + // currentType is now the array type [N x T]; currentPtr points at it. + outElemType = JamLLVMGetArrayElementType(currentType); + return JamLLVMBuildArrayGEP(ctx.getBuilder(), currentType, currentPtr, + idxVal, "idxgep"); + } + + throw std::runtime_error( + "Indexing supports only locals and struct field chains"); +} + static JamValueRef coerceTo(JamCodegenContext &ctx, JamValueRef val, JamTypeRef expected) { JamTypeRef actual = JamLLVMTypeOf(val); @@ -543,15 +674,41 @@ JamValueRef AssignExprAST::codegen(JamCodegenContext &ctx) { return rhsVal; } + // Array element target: arr[i] = value; or game.board[i] = value; + if (auto *idxExpr = dynamic_cast(Target.get())) { + JamValueRef idxVal = idxExpr->getIndex()->codegen(ctx); + if (!idxVal) return nullptr; + idxVal = coerceTo(ctx, idxVal, ctx.getInt64Type()); + JamTypeRef elemType = nullptr; + JamValueRef elemPtr = + resolveIndexedElementPtr(ctx, idxExpr->getObject(), idxVal, + elemType); + rhsVal = coerceTo(ctx, rhsVal, elemType); + JamLLVMBuildStore(ctx.getBuilder(), rhsVal, elemPtr); + return rhsVal; + } + throw std::runtime_error("Invalid assignment target"); } +JamValueRef IndexExprAST::codegen(JamCodegenContext &ctx) { + JamValueRef idxVal = Index->codegen(ctx); + if (!idxVal) return nullptr; + idxVal = coerceTo(ctx, idxVal, ctx.getInt64Type()); + JamTypeRef elemType = nullptr; + JamValueRef elemPtr = + resolveIndexedElementPtr(ctx, Object.get(), idxVal, elemType); + return JamLLVMBuildLoad(ctx.getBuilder(), elemType, elemPtr, "idxload"); +} + JamValueRef VarDeclAST::codegen(JamCodegenContext &ctx) { JamTypeRef VarType = ctx.getTypeFromString(Type); JamValueRef Alloca = JamLLVMBuildAlloca(ctx.getBuilder(), VarType, Name.c_str()); - if (Init) { + // `var x: T = undefined;` leaves the alloca uninitialized. + // All other initializers are evaluated and stored. + if (Init && !dynamic_cast(Init.get())) { // If the initializer is a struct literal, propagate the target type so // it can build the right struct and coerce field values. if (auto *structLit = dynamic_cast(Init.get())) { @@ -561,10 +718,6 @@ JamValueRef VarDeclAST::codegen(JamCodegenContext &ctx) { JamValueRef InitVal = Init->codegen(ctx); if (!InitVal) return nullptr; JamLLVMBuildStore(ctx.getBuilder(), InitVal, Alloca); - } else { - // Initialize with zero/null value - JamValueRef ZeroVal = JamLLVMConstNull(VarType); - JamLLVMBuildStore(ctx.getBuilder(), ZeroVal, Alloca); } ctx.setVariable(Name, Alloca); diff --git a/src/ast.h b/src/ast.h index e30f4ea..dc314fa 100644 --- a/src/ast.h +++ b/src/ast.h @@ -25,14 +25,18 @@ class ExprAST { virtual JamValueRef codegen(JamCodegenContext &ctx) = 0; }; -// Number literal +// Number literal — stays untyped until the use site sets an expected type. +// If no expected type is set, falls back to the smallest integer type that +// fits the literal value. class NumberExprAST : public ExprAST { uint64_t Val; bool IsNegative; + JamTypeRef ExpectedType = nullptr; public: NumberExprAST(uint64_t Val, bool IsNegative = false) : Val(Val), IsNegative(IsNegative) {} + void setExpectedType(JamTypeRef ty) { ExpectedType = ty; } JamValueRef codegen(JamCodegenContext &ctx) override; }; @@ -45,6 +49,15 @@ class BooleanExprAST : public ExprAST { JamValueRef codegen(JamCodegenContext &ctx) override; }; +// `undefined` literal — marks a binding's storage as left uninitialized. +// Only meaningful as a VarDeclAST initializer; calling codegen() directly +// throws. +class UndefinedExprAST : public ExprAST { + public: + UndefinedExprAST() {} + JamValueRef codegen(JamCodegenContext &ctx) override; +}; + // String literal class StringLiteralExprAST : public ExprAST { std::string Val; @@ -224,6 +237,21 @@ class MemberAccessExprAST : public ExprAST { ExprAST *getObject() const { return Object.get(); } }; +// Array indexing: obj[idx] +class IndexExprAST : public ExprAST { + std::unique_ptr Object; + std::unique_ptr Index; + + public: + IndexExprAST(std::unique_ptr Object, + std::unique_ptr Index) + : Object(std::move(Object)), Index(std::move(Index)) {} + JamValueRef codegen(JamCodegenContext &ctx) override; + + ExprAST *getObject() const { return Object.get(); } + ExprAST *getIndex() const { return Index.get(); } +}; + // Struct literal expression - { x: 0, y: 100, z: 50 } class StructLiteralExprAST : public ExprAST { std::string TypeName; // Filled in by VarDeclAST when target type is known diff --git a/src/codegen.cpp b/src/codegen.cpp index cd74b2f..24550f6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -59,6 +59,17 @@ JamCodegenContext::getTypeFromString(const std::string &typeStr) const { JamTypeRef usizeType = getInt64Type(); JamTypeRef elementTypes[2] = {elemPtrType, usizeType}; return JamLLVMStructType(ctx, elementTypes, 2, false); + } else if (typeStr.length() >= 3 && typeStr[0] == '[') { + // Fixed-size array: [N]T + size_t closeBracket = typeStr.find(']'); + if (closeBracket == std::string::npos || closeBracket == 1) { + throw std::runtime_error("Malformed array type: " + typeStr); + } + std::string sizeStr = typeStr.substr(1, closeBracket - 1); + std::string elementTypeStr = typeStr.substr(closeBracket + 1); + unsigned long long size = std::stoull(sizeStr); + JamTypeRef elemType = getTypeFromString(elementTypeStr); + return JamLLVMArrayType(elemType, static_cast(size)); } throw std::runtime_error("Unknown type: " + typeStr); } diff --git a/src/jam_llvm.cpp b/src/jam_llvm.cpp index d34933c..72f0a05 100644 --- a/src/jam_llvm.cpp +++ b/src/jam_llvm.cpp @@ -245,6 +245,11 @@ JamTypeRef JamLLVMArrayType(JamTypeRef elementType, unsigned elementCount) { llvm::ArrayType::get(UNWRAP_TYPE(elementType), elementCount)); } +JamTypeRef JamLLVMGetArrayElementType(JamTypeRef arrayType) { + auto *aT = llvm::cast(UNWRAP_TYPE(arrayType)); + return WRAP_TYPE(aT->getElementType()); +} + unsigned JamLLVMGetIntTypeWidth(JamTypeRef type) { return UNWRAP_TYPE(type)->getIntegerBitWidth(); } @@ -441,6 +446,26 @@ JamValueRef JamLLVMBuildStore(JamBuilderRef builder, JamValueRef val, UNWRAP_VALUE(ptr))); } +JamValueRef JamLLVMBuildArrayGEP(JamBuilderRef builder, JamTypeRef arrayType, + JamValueRef ptr, JamValueRef idx, + const char *name) { + auto *b = UNWRAP_BUILDER(builder); + llvm::Value *zero = + llvm::ConstantInt::get(llvm::Type::getInt32Ty(b->getContext()), 0); + llvm::Value *indices[2] = {zero, UNWRAP_VALUE(idx)}; + return WRAP_VALUE(b->CreateInBoundsGEP(UNWRAP_TYPE(arrayType), + UNWRAP_VALUE(ptr), + llvm::ArrayRef(indices, 2), + name)); +} + +JamValueRef JamLLVMBuildStructGEP(JamBuilderRef builder, JamTypeRef structType, + JamValueRef ptr, unsigned fieldIdx, + const char *name) { + return WRAP_VALUE(UNWRAP_BUILDER(builder)->CreateStructGEP( + UNWRAP_TYPE(structType), UNWRAP_VALUE(ptr), fieldIdx, name)); +} + // ============================================================================ // Instructions - Arithmetic // ============================================================================ diff --git a/src/jam_llvm.h b/src/jam_llvm.h index 994c718..91c7e91 100644 --- a/src/jam_llvm.h +++ b/src/jam_llvm.h @@ -212,6 +212,21 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildLoad(JamBuilderRef builder, const char *name); JAM_EXTERN_C JamValueRef JamLLVMBuildStore(JamBuilderRef builder, JamValueRef val, JamValueRef ptr); +// In-bounds GEP for indexing into a fixed-size array: gep [N x T], ptr, 0, idx. +// `arrayType` must be the array aggregate type that `ptr` points to. +JAM_EXTERN_C JamValueRef JamLLVMBuildArrayGEP(JamBuilderRef builder, + JamTypeRef arrayType, + JamValueRef ptr, JamValueRef idx, + const char *name); +// Struct field GEP: returns a pointer to field `fieldIdx` of the struct +// pointed to by `ptr`. `structType` must be the struct type `ptr` points to. +JAM_EXTERN_C JamValueRef JamLLVMBuildStructGEP(JamBuilderRef builder, + JamTypeRef structType, + JamValueRef ptr, + unsigned fieldIdx, + const char *name); +// Returns the element type of an array type (e.g. [200 x i8] -> i8). +JAM_EXTERN_C JamTypeRef JamLLVMGetArrayElementType(JamTypeRef arrayType); // ============================================================================ // Instructions - Arithmetic diff --git a/src/lexer.cpp b/src/lexer.cpp index e5c71e4..41349d3 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -122,6 +122,8 @@ void Lexer::identifier() { addToken(TOK_TFN, text); } else if (text == "struct") { addToken(TOK_STRUCT, text); + } else if (text == "undefined") { + addToken(TOK_UNDEFINED, text); } else if (text == "u1" || text == "u8" || text == "u16" || text == "u32" || text == "u64" || text == "i8" || text == "i16" || text == "i32" || text == "i64" || text == "f32" || diff --git a/src/parser.cpp b/src/parser.cpp index ea3fda1..f0c9fb4 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -60,6 +60,8 @@ std::unique_ptr Parser::parsePrimary() { return std::make_unique(true); } else if (match(TOK_FALSE)) { return std::make_unique(false); + } else if (match(TOK_UNDEFINED)) { + return std::make_unique(); } else if (match(TOK_STRING_LITERAL)) { return std::make_unique(previous().lexeme); } else if (match(TOK_IMPORT)) { @@ -114,6 +116,14 @@ std::unique_ptr Parser::parsePrimary() { return std::make_unique(callee, std::move(args)); } + // Postfix indexing: arr[i] (and chains arr[i][j]) + while (match(TOK_OPEN_BRACKET)) { + auto idx = parseLogicalOr(); + consume(TOK_CLOSE_BRACKET, "Expected ']' after index"); + expr = + std::make_unique(std::move(expr), std::move(idx)); + } + return expr; } @@ -121,14 +131,21 @@ std::unique_ptr Parser::parsePrimary() { } std::string Parser::parseType() { - // Handle slice types: []T or []const T + // Handle bracket-prefixed types: []T (slice) or [N]T (fixed array) if (match(TOK_OPEN_BRACKET)) { - consume(TOK_CLOSE_BRACKET, "Expected ']' after '['"); - // Check for []const T (like Zig) - bool isConst = match(TOK_CONST); + if (match(TOK_CLOSE_BRACKET)) { + // Slice: []T or []const T + bool isConst = match(TOK_CONST); + std::string elementType = parseType(); + if (isConst) { return "[]const " + elementType; } + return "[]" + elementType; + } + // Fixed-size array: [N]T + consume(TOK_NUMBER, "Expected size or ']' after '['"); + std::string sizeLit = previous().lexeme; + consume(TOK_CLOSE_BRACKET, "Expected ']' after array size"); std::string elementType = parseType(); - if (isConst) { return "[]const " + elementType; } - return "[]" + elementType; + return "[" + sizeLit + "]" + elementType; } // Handle const T (like Zig) if (match(TOK_CONST)) { @@ -171,8 +188,11 @@ std::unique_ptr Parser::parseExpression() { std::string type = "u8"; // Default type if (match(TOK_COLON)) { type = parseType(); } - std::unique_ptr init = nullptr; - if (match(TOK_EQUAL)) { init = parseLogicalOr(); } + // Initializer is required. Use `= undefined` to leave storage + // uninitialized. + consume(TOK_EQUAL, + "Expected '=' (use `= undefined` to leave uninitialized)"); + std::unique_ptr init = parseLogicalOr(); consume(TOK_SEMI, "Expected ';' after variable declaration"); return std::make_unique(name, type, isConst, @@ -318,7 +338,7 @@ std::unique_ptr Parser::parseComparison() { return LHS; } -// Bitwise &, |, ^ — same precedence level, left associative (Zig-style) +// Bitwise &, |, ^ — same precedence level, left associative std::unique_ptr Parser::parseBitwise() { auto LHS = parseShift(); diff --git a/src/token.h b/src/token.h index bbad262..6b5d21e 100644 --- a/src/token.h +++ b/src/token.h @@ -58,12 +58,13 @@ enum TokenType { TOK_NOT, // ! (logical NOT) TOK_TFN, // tfn keyword (test function) TOK_STRUCT, // struct keyword - TOK_AMP, // & (bitwise AND) - TOK_PIPE, // | (bitwise OR) - TOK_CARET, // ^ (bitwise XOR) - TOK_TILDE, // ~ (bitwise NOT) - TOK_LSHIFT, // << (left shift) - TOK_RSHIFT, // >> (right shift) + TOK_AMP, // & (bitwise AND) + TOK_PIPE, // | (bitwise OR) + TOK_CARET, // ^ (bitwise XOR) + TOK_TILDE, // ~ (bitwise NOT) + TOK_LSHIFT, // << (left shift) + TOK_RSHIFT, // >> (right shift) + TOK_UNDEFINED, // undefined keyword (uninitialized storage marker) }; // Token structure diff --git a/tests/unit/test_array.jam b/tests/unit/test_array.jam new file mode 100644 index 0000000..55f6ff0 --- /dev/null +++ b/tests/unit/test_array.jam @@ -0,0 +1,127 @@ +const { assert } = import("test"); + +const Game = struct { + score: u8, + board: [10]u8, +}; + +fn writeThenReadU8() u8 { + var arr: [10]u8 = undefined; + arr[0] = 42; + arr[1] = 7; + arr[9] = 200; + return arr[0]; +} + +fn secondElement() u8 { + var arr: [10]u8 = undefined; + arr[0] = 42; + arr[1] = 7; + arr[9] = 200; + return arr[1]; +} + +fn lastElement() u8 { + var arr: [10]u8 = undefined; + arr[0] = 42; + arr[1] = 7; + arr[9] = 200; + return arr[9]; +} + +fn sumFirstFour() u32 { + var arr: [10]u32 = undefined; + arr[0] = 100; + arr[1] = 200; + arr[2] = 300; + arr[3] = 400; + return arr[0] + arr[1]; +} + +fn dynamicIndexRead() u8 { + var arr: [4]u8 = undefined; + arr[0] = 10; + arr[1] = 20; + arr[2] = 30; + arr[3] = 40; + var i: u8 = 2; + return arr[i]; +} + +fn loopFillThenRead() u8 { + var arr: [16]u8 = undefined; + for i in 0:16 { + arr[i] = i; + } + return arr[7]; +} + +fn loopFillThenReadLast() u8 { + var arr: [16]u8 = undefined; + for i in 0:16 { + arr[i] = i; + } + return arr[15]; +} + +fn structFieldArrayWrite() u8 { + var g: Game = undefined; + g.board[3] = 42; + return g.board[3]; +} + +fn structFieldArrayLoopFill() u8 { + var g: Game = undefined; + for i in 0:10 { + g.board[i] = i; + } + return g.board[7]; +} + +fn structFieldArrayMixed() u8 { + var g: Game = undefined; + g.score = 99; + g.board[0] = g.score; + g.board[1] = 50; + return g.board[0]; +} + +tfn writeThenReadU8() { + assert(writeThenReadU8(), 42); +} + +tfn secondElement() { + assert(secondElement(), 7); +} + +tfn lastElement() { + assert(lastElement(), 200); +} + +tfn sumFirstFour() { + assert(sumFirstFour(), 300); +} + +tfn dynamicIndexRead() { + assert(dynamicIndexRead(), 30); +} + +tfn loopFillThenRead() { + assert(loopFillThenRead(), 7); +} + +tfn loopFillThenReadLast() { + assert(loopFillThenReadLast(), 15); +} + +tfn structFieldArrayWrite() { + assert(structFieldArrayWrite(), 42); +} + +tfn structFieldArrayLoopFill() { + assert(structFieldArrayLoopFill(), 7); +} + +tfn structFieldArrayMixed() { + assert(structFieldArrayMixed(), 99); +} diff --git a/tests/unit/test_bitwise.jam b/tests/unit/test_bitwise.jam new file mode 100644 index 0000000..513c552 --- /dev/null +++ b/tests/unit/test_bitwise.jam @@ -0,0 +1,95 @@ +const { assert } = import("test"); + +fn bandU8(a: u8, b: u8) u8 { + return a & b; +} + +fn borU8(a: u8, b: u8) u8 { + return a | b; +} + +fn bxorU8(a: u8, b: u8) u8 { + return a ^ b; +} + +fn bnotU8(a: u8) u8 { + return ~a; +} + +fn shlU8(a: u8, b: u8) u8 { + return a << b; +} + +fn shrU8(a: u8, b: u8) u8 { + return a >> b; +} + +fn bandU32(a: u32, b: u32) u32 { + return a & b; +} + +fn shlU32(a: u32, b: u32) u32 { + return a << b; +} + +fn shrU32(a: u32, b: u32) u32 { + return a >> b; +} + +fn bnotU32(a: u32) u32 { + return ~a; +} + +tfn bandU8() { + assert(bandU8(255, 15), 15); + assert(bandU8(170, 85), 0); + assert(bandU8(204, 170), 136); +} + +tfn borU8() { + assert(borU8(240, 15), 255); + assert(borU8(0, 0), 0); + assert(borU8(170, 85), 255); +} + +tfn bxorU8() { + assert(bxorU8(255, 15), 240); + assert(bxorU8(170, 170), 0); + assert(bxorU8(204, 170), 102); +} + +tfn bnotU8() { + assert(bnotU8(0), 255); + assert(bnotU8(15), 240); + assert(bnotU8(255), 0); +} + +tfn shlU8() { + assert(shlU8(1, 4), 16); + assert(shlU8(3, 2), 12); + assert(shlU8(1, 7), 128); +} + +tfn shrU8() { + assert(shrU8(255, 4), 15); + assert(shrU8(16, 1), 8); + assert(shrU8(128, 7), 1); +} + +tfn bandU32() { + assert(bandU32(4294967295, 16711935), 16711935); +} + +tfn shlU32() { + assert(shlU32(1, 16), 65536); + assert(shlU32(7, 20), 7340032); +} + +tfn shrU32() { + assert(shrU32(65536, 16), 1); + assert(shrU32(4294967295, 24), 255); +} + +tfn bnotU32() { + assert(bnotU32(0), 4294967295); +} diff --git a/tests/unit/test_extern_call.jam b/tests/unit/test_extern_call.jam new file mode 100644 index 0000000..b84da24 --- /dev/null +++ b/tests/unit/test_extern_call.jam @@ -0,0 +1,17 @@ +extern fn abs(x: i32) i32; + +fn callAbsNeg() i32 { + return abs(-7); +} + +fn callAbsPos() i32 { + return abs(42); +} + +tfn callAbsNeg() { + assert(callAbsNeg(), 7); +} + +tfn callAbsPos() { + assert(callAbsPos(), 42); +} diff --git a/tests/unit/test_mixed_slices.jam b/tests/unit/test_mixed_slices.jam index b779fbd..c5c2b85 100644 --- a/tests/unit/test_mixed_slices.jam +++ b/tests/unit/test_mixed_slices.jam @@ -20,17 +20,17 @@ fn test_string_functions(input: str) str { // Test complex slice types fn test_i8_slice() []i8 { - var data: []i8; + var data: []i8 = undefined; return data; } fn test_i16_slice() []i16 { - var data: []i16; + var data: []i16 = undefined; return data; } fn test_i32_slice() []i32 { - var data: []i32; + var data: []i32 = undefined; return data; } diff --git a/tests/unit/test_slices.jam b/tests/unit/test_slices.jam index 450748e..12599d4 100644 --- a/tests/unit/test_slices.jam +++ b/tests/unit/test_slices.jam @@ -27,22 +27,22 @@ fn test_u8_slice_const() []u8 { fn test_u16_slice() []u16 { // This would need array literal syntax in the future // For now, just test the type parsing - var data: []u16; + var data: []u16 = undefined; return data; } fn test_u32_slice() []u32 { - var data: []u32; + var data: []u32 = undefined; return data; } fn test_bool_slice() []bool { - var data: []bool; + var data: []bool = undefined; return data; } // Test nested slice types (for future) fn test_slice_of_slices() [][]u8 { - var data: [][]u8; + var data: [][]u8 = undefined; return data; } \ No newline at end of file