Fix some long-standing tech debt
7bfd64dd51d19a55f370068c8d96a85af244b559392a6ec148b1cf8321c9fb47
1 parent
e0e2e01d
compiler/radiance.rad
+3 -3
| 542 | 542 | funcPath[j - 1] = desc.modPath[j]; |
|
| 543 | 543 | } |
|
| 544 | 544 | funcPath[desc.modPath.len - 1] = desc.fnName; |
|
| 545 | 545 | let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]); |
|
| 546 | 546 | ||
| 547 | - | let a = ast::nodeAllocator(arena); |
|
| 547 | + | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 548 | 548 | let args = ast::nodeSlice(arena, 3) |
|
| 549 | 549 | .append(modArg, a) |
|
| 550 | 550 | .append(nameArg, a) |
|
| 551 | 551 | .append(funcArg, a); |
|
| 552 | 552 |
| 599 | 599 | } |
|
| 600 | 600 | ||
| 601 | 601 | /// Synthesize the test entry point. |
|
| 602 | 602 | fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node { |
|
| 603 | 603 | // Build array literal: `[testing::test(...), ...]`. |
|
| 604 | - | let a = ast::nodeAllocator(arena); |
|
| 604 | + | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 605 | 605 | let mut elements = ast::nodeSlice(arena, tests.len as u32); |
|
| 606 | 606 | for i in 0..tests.len { |
|
| 607 | 607 | elements.append(synthTestCall(arena, &tests[i]), a); |
|
| 608 | 608 | } |
|
| 609 | 609 | let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements)); |
| 653 | 653 | decl: *ast::Node |
|
| 654 | 654 | ) { |
|
| 655 | 655 | let case ast::NodeValue::Block(block) = blockNode.value else { |
|
| 656 | 656 | panic "injectIntoBlock: expected Block node"; |
|
| 657 | 657 | }; |
|
| 658 | - | let stmts = block.statements.append(decl, ast::nodeAllocator(arena)); |
|
| 658 | + | let stmts = block.statements.append(decl, alloc::arenaAllocator(&mut arena.arena)); |
|
| 659 | 659 | blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts }); |
|
| 660 | 660 | } |
|
| 661 | 661 | ||
| 662 | 662 | /// Write code buffer to file as raw bytes. |
|
| 663 | 663 | fn writeCode(code: *[u32], path: *[u8]) -> bool { |
lib/std/arch/rv64.rad
+0 -1
| 131 | 131 | ||
| 132 | 132 | /// Get target configuration for register allocation. |
|
| 133 | 133 | // TODO: This should be a constant variable. |
|
| 134 | 134 | pub fn targetConfig() -> regalloc::TargetConfig { |
|
| 135 | 135 | return regalloc::TargetConfig { |
|
| 136 | - | // TODO: Use inline slice when we move to self-hosted compiler. |
|
| 137 | 136 | allocatable: &ALLOCATABLE_REGS[..], |
|
| 138 | 137 | argRegs: &ARG_REG_NUMS[..], |
|
| 139 | 138 | calleeSaved: &CALLEE_SAVED_NUMS[..], |
|
| 140 | 139 | slotSize: DWORD_SIZE, |
|
| 141 | 140 | }; |
lib/std/lang/ast.rad
+1 -7
| 33 | 33 | let ptr = try! alloc::allocSlice(&mut arena.arena, @sizeOf(*Node), @alignOf(*Node), capacity); |
|
| 34 | 34 | ||
| 35 | 35 | return @sliceOf(ptr.ptr as *mut *Node, 0, capacity); |
|
| 36 | 36 | } |
|
| 37 | 37 | ||
| 38 | - | /// Return an allocator backed by the node arena. |
|
| 39 | - | // TODO: Why do we need this? |
|
| 40 | - | pub fn nodeAllocator(arena: *mut NodeArena) -> alloc::Allocator { |
|
| 41 | - | return alloc::arenaAllocator(&mut arena.arena); |
|
| 42 | - | } |
|
| 43 | - | ||
| 44 | 38 | /// Attribute bit set applied to declarations or fields. |
|
| 45 | 39 | pub union Attribute { |
|
| 46 | 40 | /// Public visibility attribute. |
|
| 47 | 41 | Pub = 0b1, |
|
| 48 | 42 | /// Default implementation attribute. |
| 848 | 842 | ||
| 849 | 843 | /// Synthesize a module with a function in it with the given name and statements. |
|
| 850 | 844 | pub fn synthFnModule( |
|
| 851 | 845 | arena: *mut NodeArena, name: *[u8], bodyStmts: *mut [*Node] |
|
| 852 | 846 | ) -> SynthFnMod { |
|
| 853 | - | let a = nodeAllocator(arena); |
|
| 847 | + | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 854 | 848 | let fnName = synthNode(arena, NodeValue::Ident(name)); |
|
| 855 | 849 | let params: *mut [*Node] = &mut []; |
|
| 856 | 850 | let throwList: *mut [*Node] = &mut []; |
|
| 857 | 851 | let fnSig = FnSig { params, returnType: nil, throwList }; |
|
| 858 | 852 | let fnBody = synthNode(arena, NodeValue::Block(Block { statements: bodyStmts })); |
lib/std/lang/gen/data.rad
+6 -15
| 97 | 97 | }, |
|
| 98 | 98 | case il::DataItem::Sym(name) => { |
|
| 99 | 99 | let addr = lookupAddr(dataSymMap, name) else { |
|
| 100 | 100 | panic "emitSection: data symbol not found"; |
|
| 101 | 101 | }; |
|
| 102 | - | // Write 8-byte pointer: low 4 bytes are the |
|
| 103 | - | // address, high 4 bytes are zero. |
|
| 104 | - | // TODO: Use `u64` once it's supported. |
|
| 105 | - | let lo: u32 = addr; |
|
| 106 | - | let hi: u32 = 0; |
|
| 107 | - | let loPtr = &lo as *u8; |
|
| 108 | - | let hiPtr = &hi as *u8; |
|
| 102 | + | let addr64: u64 = addr as u64; |
|
| 103 | + | let addrPtr = &addr64 as *u8; |
|
| 109 | 104 | ||
| 110 | - | try! mem::copy(&mut buf[offset..], @sliceOf(loPtr, 4)); |
|
| 111 | - | try! mem::copy(&mut buf[(offset + 4)..], @sliceOf(hiPtr, 4)); |
|
| 105 | + | try! mem::copy(&mut buf[offset..], @sliceOf(addrPtr, 8)); |
|
| 112 | 106 | ||
| 113 | 107 | offset += 8; |
|
| 114 | 108 | }, |
|
| 115 | 109 | case il::DataItem::Fn(name) => { |
|
| 116 | 110 | let addr = codeBase + labels::funcOffset(fnLabels, name) as u32; |
|
| 117 | - | let lo: u32 = addr; |
|
| 118 | - | let hi: u32 = 0; |
|
| 119 | - | let loPtr = &lo as *u8; |
|
| 120 | - | let hiPtr = &hi as *u8; |
|
| 111 | + | let addr64: u64 = addr as u64; |
|
| 112 | + | let addrPtr = &addr64 as *u8; |
|
| 121 | 113 | ||
| 122 | - | try! mem::copy(&mut buf[offset..], @sliceOf(loPtr, 4)); |
|
| 123 | - | try! mem::copy(&mut buf[(offset + 4)..], @sliceOf(hiPtr, 4)); |
|
| 114 | + | try! mem::copy(&mut buf[offset..], @sliceOf(addrPtr, 8)); |
|
| 124 | 115 | ||
| 125 | 116 | offset += 8; |
|
| 126 | 117 | }, |
|
| 127 | 118 | case il::DataItem::Str(s) => { |
|
| 128 | 119 | try! mem::copy(&mut buf[offset..], s); |
lib/std/lang/gen/regalloc/liveness.rad
+0 -1
| 27 | 27 | use std::lang::il; |
|
| 28 | 28 | use std::lang::alloc; |
|
| 29 | 29 | use std::lang::gen::bitset; |
|
| 30 | 30 | ||
| 31 | 31 | /// Maximum number of SSA registers supported. |
|
| 32 | - | // TODO: Why so many? |
|
| 33 | 32 | pub const MAX_SSA_REGS: u32 = 8192; |
|
| 34 | 33 | ||
| 35 | 34 | /// Liveness information for a function. |
|
| 36 | 35 | pub record LiveInfo { |
|
| 37 | 36 | /// Per-block live-in sets (indexed by block index). |
lib/std/lang/lower.rad
+35 -51
| 973 | 973 | let mut pos: u32 = 0; |
|
| 974 | 974 | ||
| 975 | 975 | pos += try! mem::copy(&mut buf[pos..], typeName); |
|
| 976 | 976 | pos += try! mem::copy(&mut buf[pos..], "::"); |
|
| 977 | 977 | pos += try! mem::copy(&mut buf[pos..], methodName); |
|
| 978 | - | ||
| 979 | - | // TODO: Assert that `pos` equals `totalLen`. |
|
| 978 | + | assert pos == totalLen; |
|
| 980 | 979 | ||
| 981 | 980 | return qualifyName(self, modId, &buf[..totalLen]); |
|
| 982 | 981 | } |
|
| 983 | 982 | ||
| 984 | 983 | /// Build a v-table data name of the form "vtable::Type::Trait". |
| 991 | 990 | ||
| 992 | 991 | pos += try! mem::copy(&mut buf[pos..], prefix); |
|
| 993 | 992 | pos += try! mem::copy(&mut buf[pos..], typeName); |
|
| 994 | 993 | pos += try! mem::copy(&mut buf[pos..], "::"); |
|
| 995 | 994 | pos += try! mem::copy(&mut buf[pos..], traitName); |
|
| 996 | - | ||
| 997 | - | // TODO: Assert that `pos` equals `totalLen`. |
|
| 995 | + | assert pos == totalLen; |
|
| 998 | 996 | ||
| 999 | 997 | return qualifyName(self, modId, &buf[..totalLen]); |
|
| 1000 | 998 | } |
|
| 1001 | 999 | ||
| 1002 | 1000 | /// Lower an instance declaration (`instance Trait for Type { ... }`). |
| 3359 | 3357 | defaultIdx = i; |
|
| 3360 | 3358 | } |
|
| 3361 | 3359 | case ast::ProngArm::Case(pats) => { |
|
| 3362 | 3360 | blocks[i] = try createBlock(self, "case"); |
|
| 3363 | 3361 | for pat in pats { |
|
| 3364 | - | let cv = resolver::constValueFor(self.low.resolver, pat) |
|
| 3362 | + | let cv = resolver::constValueEntry(self.low.resolver, pat) |
|
| 3365 | 3363 | else throw LowerError::MissingConst(pat); |
|
| 3366 | 3364 | ||
| 3367 | 3365 | cases.append(il::SwitchCase { |
|
| 3368 | 3366 | value: constToScalar(cv), |
|
| 3369 | 3367 | target: blocks[i].n, |
| 4246 | 4244 | let lenEq = try emitEqAtOffset(self, a, b, offset + SLICE_LEN_OFFSET, resolver::Type::U32); |
|
| 4247 | 4245 | ||
| 4248 | 4246 | return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, ptrEq, lenEq); |
|
| 4249 | 4247 | } |
|
| 4250 | 4248 | ||
| 4251 | - | /// Check if a type may contain uninitialized payload bytes when used as the |
|
| 4252 | - | /// inner type of a `nil` optional. Unions with non-void variants and nested |
|
| 4253 | - | /// optionals fall into this category; records and primitives do not. |
|
| 4254 | - | fn needsGuardedPayloadCmp(inner: resolver::Type) -> bool { |
|
| 4255 | - | if let _ = unionInfoFromType(inner) { |
|
| 4256 | - | return true; |
|
| 4257 | - | } |
|
| 4258 | - | match inner { |
|
| 4259 | - | case resolver::Type::Optional(_) => return true, |
|
| 4260 | - | else => return false, |
|
| 4261 | - | } |
|
| 4262 | - | } |
|
| 4263 | - | ||
| 4264 | 4249 | /// Compare two optional aggregate values for equality. |
|
| 4265 | 4250 | /// |
|
| 4266 | 4251 | /// Two optionals are equal when their tags match and either both are `nil` or |
|
| 4267 | 4252 | /// their payloads are equal. |
|
| 4268 | 4253 | /// |
| 4284 | 4269 | // Load tags. |
|
| 4285 | 4270 | let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8); |
|
| 4286 | 4271 | let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8); |
|
| 4287 | 4272 | ||
| 4288 | 4273 | // For simple inner types (no unions/nested optionals), use branchless comparison. |
|
| 4289 | - | // TODO: Inline this function. |
|
| 4290 | - | if not needsGuardedPayloadCmp(inner) { |
|
| 4274 | + | // Unions and nested optionals may contain uninitialized payload bytes |
|
| 4275 | + | // when nil, so they need a guarded comparison. |
|
| 4276 | + | let isUnion = unionInfoFromType(inner) != nil; |
|
| 4277 | + | let mut isOptional = false; |
|
| 4278 | + | if let case resolver::Type::Optional(_) = inner { |
|
| 4279 | + | isOptional = true; |
|
| 4280 | + | } |
|
| 4281 | + | if not isUnion and not isOptional { |
|
| 4291 | 4282 | let tagEq = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB); |
|
| 4292 | 4283 | let tagNil = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, il::Val::Imm(0)); |
|
| 4293 | 4284 | let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner); |
|
| 4294 | 4285 | ||
| 4295 | 4286 | return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, tagEq, |
| 4531 | 4522 | try lowerRecordFields(self, dst, &recInfo, lit.fields, 0); |
|
| 4532 | 4523 | ||
| 4533 | 4524 | return il::Val::Reg(dst); |
|
| 4534 | 4525 | } |
|
| 4535 | 4526 | case resolver::Type::Nominal(resolver::NominalType::Union(_)) => { |
|
| 4536 | - | // TODO: This can be inlined once we have a real register allocator. |
|
| 4537 | - | return try lowerUnionRecordLit(self, typ, lit); |
|
| 4527 | + | let typeName = lit.typeName else { |
|
| 4528 | + | throw LowerError::ExpectedVariant; |
|
| 4529 | + | }; |
|
| 4530 | + | let sym = try symOf(self, typeName); |
|
| 4531 | + | let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else { |
|
| 4532 | + | throw LowerError::ExpectedVariant; |
|
| 4533 | + | }; |
|
| 4534 | + | let recInfo = recordInfoFromType(payloadType) else { |
|
| 4535 | + | throw LowerError::ExpectedRecord; |
|
| 4536 | + | }; |
|
| 4537 | + | let unionInfo = unionInfoFromType(typ) else { |
|
| 4538 | + | throw LowerError::MissingMetadata; |
|
| 4539 | + | }; |
|
| 4540 | + | let valOffset = unionInfo.valOffset as i32; |
|
| 4541 | + | let dst = try emitReserve(self, typ); |
|
| 4542 | + | ||
| 4543 | + | emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET); |
|
| 4544 | + | try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset); |
|
| 4545 | + | ||
| 4546 | + | return il::Val::Reg(dst); |
|
| 4538 | 4547 | } |
|
| 4539 | 4548 | else => throw LowerError::UnexpectedType(&typ), |
|
| 4540 | 4549 | } |
|
| 4541 | 4550 | } |
|
| 4542 | 4551 | ||
| 4543 | - | /// Lower a union variant record literal like `Union::Variant { field: value }`. |
|
| 4544 | - | fn lowerUnionRecordLit(self: *mut FnLowerer, typ: resolver::Type, lit: ast::RecordLit) -> il::Val throws (LowerError) { |
|
| 4545 | - | let typeName = lit.typeName else { |
|
| 4546 | - | throw LowerError::ExpectedVariant; |
|
| 4547 | - | }; |
|
| 4548 | - | let sym = try symOf(self, typeName); |
|
| 4549 | - | let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else { |
|
| 4550 | - | throw LowerError::ExpectedVariant; |
|
| 4551 | - | }; |
|
| 4552 | - | let recInfo = recordInfoFromType(payloadType) else { |
|
| 4553 | - | throw LowerError::ExpectedRecord; |
|
| 4554 | - | }; |
|
| 4555 | - | let unionInfo = unionInfoFromType(typ) else { |
|
| 4556 | - | throw LowerError::MissingMetadata; |
|
| 4557 | - | }; |
|
| 4558 | - | let valOffset = unionInfo.valOffset as i32; |
|
| 4559 | - | let dst = try emitReserve(self, typ); |
|
| 4560 | - | ||
| 4561 | - | emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET); |
|
| 4562 | - | try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset); |
|
| 4563 | - | ||
| 4564 | - | return il::Val::Reg(dst); |
|
| 4565 | - | } |
|
| 4566 | - | ||
| 4567 | 4552 | /// Lower fields of a record literal into a destination register. |
|
| 4568 | 4553 | /// The `offset` is added to each field's offset when storing. |
|
| 4569 | 4554 | fn lowerRecordFields( |
|
| 4570 | 4555 | self: *mut FnLowerer, |
|
| 4571 | 4556 | dst: il::Reg, |
| 5851 | 5836 | let dst = nextReg(self); |
|
| 5852 | 5837 | let mut needsExt: bool = false; |
|
| 5853 | 5838 | ||
| 5854 | 5839 | match unop.op { |
|
| 5855 | 5840 | case ast::UnaryOp::Not => { |
|
| 5856 | - | // TODO: Is this the best way? |
|
| 5857 | 5841 | emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a: val, b: il::Val::Imm(0) }); |
|
| 5858 | 5842 | } |
|
| 5859 | 5843 | case ast::UnaryOp::Neg => { |
|
| 5860 | 5844 | emit(self, il::Instr::UnOp { op: il::UnOp::Neg, typ, dst, a: val }); |
|
| 5861 | 5845 | needsExt = true; |
| 5923 | 5907 | /// Lower a builtin call expression. |
|
| 5924 | 5908 | fn lowerBuiltinCall(self: *mut FnLowerer, node: *ast::Node, kind: ast::Builtin, args: *mut [*ast::Node]) -> il::Val throws (LowerError) { |
|
| 5925 | 5909 | match kind { |
|
| 5926 | 5910 | case ast::Builtin::SliceOf => return try lowerSliceOf(self, node, args), |
|
| 5927 | 5911 | case ast::Builtin::SizeOf, ast::Builtin::AlignOf => { |
|
| 5928 | - | let constVal = resolver::constValueFor(self.low.resolver, node) else { |
|
| 5912 | + | let constVal = resolver::constValueEntry(self.low.resolver, node) else { |
|
| 5929 | 5913 | throw LowerError::MissingConst(node); |
|
| 5930 | 5914 | }; |
|
| 5931 | 5915 | return try constValueToVal(self, constVal, node); |
|
| 5932 | 5916 | } |
|
| 5933 | 5917 | } |
| 6731 | 6715 | } |
|
| 6732 | 6716 | ||
| 6733 | 6717 | /// Lower an identifier that refers to a global symbol. |
|
| 6734 | 6718 | fn lowerGlobalSymbol(self: *mut FnLowerer, node: *ast::Node) -> il::Val throws (LowerError) { |
|
| 6735 | 6719 | // First try to get a compile-time constant value. |
|
| 6736 | - | if let constVal = resolver::constValueFor(self.low.resolver, node) { |
|
| 6720 | + | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
|
| 6737 | 6721 | return try constValueToVal(self, constVal, node); |
|
| 6738 | 6722 | } |
|
| 6739 | 6723 | // Otherwise get the symbol. |
|
| 6740 | 6724 | let sym = try symOf(self, node); |
|
| 6741 | 6725 | let mut ty: resolver::Type = undefined; |
| 6772 | 6756 | ||
| 6773 | 6757 | /// Lower a scope access expression like `Module::Const` or `Union::Variant`. |
|
| 6774 | 6758 | /// This doesn't handle record literal variants. |
|
| 6775 | 6759 | fn lowerScopeAccess(self: *mut FnLowerer, node: *ast::Node) -> il::Val throws (LowerError) { |
|
| 6776 | 6760 | // First try to get a compile-time constant value. |
|
| 6777 | - | if let constVal = resolver::constValueFor(self.low.resolver, node) { |
|
| 6761 | + | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
|
| 6778 | 6762 | return try constValueToVal(self, constVal, node); |
|
| 6779 | 6763 | } |
|
| 6780 | 6764 | // Otherwise get the associated symbol. |
|
| 6781 | 6765 | let data = resolver::nodeData(self.low.resolver, node); |
|
| 6782 | 6766 | let sym = data.sym else { |
| 6902 | 6886 | case ast::NodeValue::Try(t) => { |
|
| 6903 | 6887 | val = try lowerTry(self, node, t); |
|
| 6904 | 6888 | } |
|
| 6905 | 6889 | case ast::NodeValue::FieldAccess(access) => { |
|
| 6906 | 6890 | // Check for compile-time constant (e.g., `arr.len` on fixed-size arrays). |
|
| 6907 | - | if let constVal = resolver::constValueFor(self.low.resolver, node) { |
|
| 6891 | + | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
|
| 6908 | 6892 | match constVal { |
|
| 6909 | 6893 | // TODO: Handle `u32` values that don't fit in an `i32`. |
|
| 6910 | 6894 | // Perhaps just store the `ConstInt`. |
|
| 6911 | 6895 | case resolver::ConstValue::Int(i) => val = il::Val::Imm(constIntToI64(i)), |
|
| 6912 | 6896 | else => val = try lowerFieldAccess(self, access), |
lib/std/lang/module.rad
+1 -1
| 302 | 302 | } |
|
| 303 | 303 | let childName = childPath[childPath.len - 1]; |
|
| 304 | 304 | ||
| 305 | 305 | // Navigate through all but the last segment to find the parent. |
|
| 306 | 306 | let mut parentId = root; |
|
| 307 | - | for part in &childPath[..(childPath.len - 1)] { |
|
| 307 | + | for part in &childPath[..childPath.len - 1] { |
|
| 308 | 308 | let child = findChild(graph, part, parentId) else { |
|
| 309 | 309 | throw ModuleError::MissingParent; |
|
| 310 | 310 | }; |
|
| 311 | 311 | parentId = child.id; |
|
| 312 | 312 | } |
lib/std/lang/parser.rad
+1 -3
| 159 | 159 | scanner: scanner::scanner(sourceLoc, source, pool), |
|
| 160 | 160 | current: scanner::invalid(0, ""), |
|
| 161 | 161 | previous: scanner::invalid(0, ""), |
|
| 162 | 162 | errors: ErrorList { list: undefined, count: 0 }, |
|
| 163 | 163 | arena, |
|
| 164 | - | allocator: ast::nodeAllocator(arena), |
|
| 164 | + | allocator: alloc::arenaAllocator(&mut arena.arena), |
|
| 165 | 165 | context: Context::Normal, |
|
| 166 | 166 | }; |
|
| 167 | 167 | } |
|
| 168 | 168 | ||
| 169 | 169 | /// Emit a `true` or `false` literal node. |
| 2268 | 2268 | ||
| 2269 | 2269 | let mut blk = mkBlock(p, 512); |
|
| 2270 | 2270 | try parseStmtsUntil(p, scanner::TokenKind::Eof, &mut blk); |
|
| 2271 | 2271 | consume(p, scanner::TokenKind::Eof); |
|
| 2272 | 2272 | ||
| 2273 | - | // TODO: Check that there are no further tokens remaining to parse. |
|
| 2274 | - | ||
| 2275 | 2273 | return node(p, ast::NodeValue::Block(blk)); |
|
| 2276 | 2274 | } |
|
| 2277 | 2275 | ||
| 2278 | 2276 | /// Consume a token of the given kind if present. |
|
| 2279 | 2277 | pub fn consume(p: *mut Parser, kind: scanner::TokenKind) -> bool { |
lib/std/lang/resolver.rad
+60 -37
| 759 | 759 | /// Diagnostics recorded so far. |
|
| 760 | 760 | errors: *mut [Error], |
|
| 761 | 761 | /// Module graph for the current package. |
|
| 762 | 762 | moduleGraph: *module::ModuleGraph, |
|
| 763 | 763 | /// Cache of module scopes indexed by module ID. |
|
| 764 | - | // TODO: Why is this optional? |
|
| 765 | 764 | moduleScopes: [?*mut Scope; module::MAX_MODULES], |
|
| 766 | 765 | /// Trait instance registry. |
|
| 767 | 766 | instances: [InstanceEntry; MAX_INSTANCES], |
|
| 768 | 767 | /// Number of registered instances. |
|
| 769 | 768 | instancesLen: u32, |
| 1174 | 1173 | /// Retrieve the constant value associated with a node, if any. |
|
| 1175 | 1174 | pub fn constValueEntry(self: *Resolver, node: *ast::Node) -> ?ConstValue { |
|
| 1176 | 1175 | return self.nodeData.entries[node.id].constValue; |
|
| 1177 | 1176 | } |
|
| 1178 | 1177 | ||
| 1179 | - | /// Get the constant value bound to a node, if present. |
|
| 1180 | - | // TODO: Use `constValueEntry` |
|
| 1181 | - | pub fn constValueFor(self: *Resolver, node: *ast::Node) -> ?ConstValue { |
|
| 1182 | - | return self.nodeData.entries[node.id].constValue; |
|
| 1183 | - | } |
|
| 1184 | - | ||
| 1185 | 1178 | /// Get the resolved record field index for a record literal field node. |
|
| 1186 | 1179 | pub fn recordFieldIndexFor(self: *Resolver, node: *ast::Node) -> ?u32 { |
|
| 1187 | 1180 | if let case NodeExtra::RecordField { index } = self.nodeData.entries[node.id].extra { |
|
| 1188 | 1181 | return index; |
|
| 1189 | 1182 | } |
| 1333 | 1326 | Type::Int => return true, |
|
| 1334 | 1327 | else => return false, |
|
| 1335 | 1328 | } |
|
| 1336 | 1329 | } |
|
| 1337 | 1330 | ||
| 1331 | + | /// Check if a type is an unsigned integer type. |
|
| 1332 | + | pub fn isUnsignedIntegerType(ty: Type) -> bool { |
|
| 1333 | + | match ty { |
|
| 1334 | + | case Type::U8, Type::U16, Type::U32, Type::U64 => return true, |
|
| 1335 | + | else => return false, |
|
| 1336 | + | } |
|
| 1337 | + | } |
|
| 1338 | + | ||
| 1338 | 1339 | /// Return the maximum of two u32 values. |
|
| 1339 | 1340 | fn max(a: u32, b: u32) -> u32 { |
|
| 1340 | 1341 | if a > b { |
|
| 1341 | 1342 | return a; |
|
| 1342 | 1343 | } |
| 3034 | 3035 | expected: MAX_FN_PARAMS, |
|
| 3035 | 3036 | actual: decl.sig.params.len, |
|
| 3036 | 3037 | })); |
|
| 3037 | 3038 | } |
|
| 3038 | 3039 | for paramNode in decl.sig.params { |
|
| 3039 | - | let paramTy = try infer(self, paramNode) catch { |
|
| 3040 | + | let paramTy = try infer(self, paramNode) catch e { |
|
| 3040 | 3041 | exitFn(self); |
|
| 3041 | - | throw ResolveError::Failure; |
|
| 3042 | + | throw e; |
|
| 3042 | 3043 | }; |
|
| 3043 | 3044 | paramTypes.append(allocType(self, paramTy), a); |
|
| 3044 | 3045 | } |
|
| 3045 | 3046 | ||
| 3046 | 3047 | if decl.sig.throwList.len > MAX_FN_THROWS { |
| 3049 | 3050 | expected: MAX_FN_THROWS, |
|
| 3050 | 3051 | actual: decl.sig.throwList.len, |
|
| 3051 | 3052 | })); |
|
| 3052 | 3053 | } |
|
| 3053 | 3054 | for throwNode in decl.sig.throwList { |
|
| 3054 | - | let throwTy = try infer(self, throwNode) catch { |
|
| 3055 | + | let throwTy = try infer(self, throwNode) catch e { |
|
| 3055 | 3056 | exitFn(self); |
|
| 3056 | - | throw ResolveError::Failure; |
|
| 3057 | + | throw e; |
|
| 3057 | 3058 | }; |
|
| 3058 | 3059 | throwList.append(allocType(self, throwTy), a); |
|
| 3059 | 3060 | } |
|
| 3060 | 3061 | exitFn(self); |
|
| 3061 | 3062 | fnType.paramTypes = ¶mTypes[..]; |
| 3090 | 3091 | if isExtern { |
|
| 3091 | 3092 | throw emitError(self, node, ErrorKind::FnUnexpectedBody); |
|
| 3092 | 3093 | } |
|
| 3093 | 3094 | enterFn(self, node, fnType); // Enter function scope for body analysis. |
|
| 3094 | 3095 | ||
| 3095 | - | let bodyTy = try checkAssignable(self, body, Type::Void) catch { |
|
| 3096 | + | let bodyTy = try checkAssignable(self, body, Type::Void) catch e { |
|
| 3096 | 3097 | exitFn(self); |
|
| 3097 | - | // TODO: Propagate error. |
|
| 3098 | - | throw emitError(self, body, ErrorKind::Internal); |
|
| 3098 | + | throw e; |
|
| 3099 | 3099 | }; |
|
| 3100 | 3100 | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3101 | 3101 | exitFn(self); |
|
| 3102 | 3102 | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3103 | 3103 | } |
| 3615 | 3615 | // Enter function scope. |
|
| 3616 | 3616 | enterFn(self, node, fnType); |
|
| 3617 | 3617 | ||
| 3618 | 3618 | // Bind the receiver parameter. |
|
| 3619 | 3619 | let receiverTy = *fnType.paramTypes[0]; |
|
| 3620 | - | try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch { |
|
| 3620 | + | try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch e { |
|
| 3621 | 3621 | exitFn(self); |
|
| 3622 | - | throw ResolveError::Failure; |
|
| 3622 | + | throw e; |
|
| 3623 | 3623 | }; |
|
| 3624 | 3624 | // Bind the remaining parameters from the signature. |
|
| 3625 | 3625 | for paramNode in sig.params { |
|
| 3626 | - | let paramTy = try infer(self, paramNode) catch { |
|
| 3626 | + | let paramTy = try infer(self, paramNode) catch e { |
|
| 3627 | 3627 | exitFn(self); |
|
| 3628 | - | throw ResolveError::Failure; |
|
| 3628 | + | throw e; |
|
| 3629 | 3629 | }; |
|
| 3630 | 3630 | } |
|
| 3631 | 3631 | ||
| 3632 | 3632 | // Resolve the body. |
|
| 3633 | 3633 | let retTy = *fnType.returnType; |
|
| 3634 | - | let bodyTy = try checkAssignable(self, body, Type::Void) catch { |
|
| 3634 | + | let bodyTy = try checkAssignable(self, body, Type::Void) catch e { |
|
| 3635 | 3635 | exitFn(self); |
|
| 3636 | - | throw ResolveError::Failure; |
|
| 3636 | + | throw e; |
|
| 3637 | 3637 | }; |
|
| 3638 | 3638 | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3639 | 3639 | exitFn(self); |
|
| 3640 | 3640 | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3641 | 3641 | } |
| 4579 | 4579 | subjectTy: Type, |
|
| 4580 | 4580 | matchType: Type, |
|
| 4581 | 4581 | matchBy: MatchBy |
|
| 4582 | 4582 | ) -> Type throws (ResolveError) { |
|
| 4583 | 4583 | enterScope(self, node); |
|
| 4584 | - | let prongTy = try resolveMatchProngBody(self, prongNode, subjectTy, matchBy) catch { |
|
| 4584 | + | let prongTy = try resolveMatchProngBody(self, prongNode, subjectTy, matchBy) catch e { |
|
| 4585 | 4585 | exitScope(self); |
|
| 4586 | - | throw ResolveError::Failure; // TODO: Rethrow same error. |
|
| 4586 | + | throw e; |
|
| 4587 | 4587 | }; |
|
| 4588 | 4588 | exitScope(self); |
|
| 4589 | 4589 | setNodeType(self, node, prongTy); |
|
| 4590 | 4590 | ||
| 4591 | 4591 | return unifyBranches(matchType, prongTy); |
| 5028 | 5028 | return setNodeType(self, node, *method.fnType.returnType); |
|
| 5029 | 5029 | } |
|
| 5030 | 5030 | } |
|
| 5031 | 5031 | } |
|
| 5032 | 5032 | let case Type::Fn(info) = calleeTy else { |
|
| 5033 | - | // TODO: Emit type error. |
|
| 5034 | - | panic; |
|
| 5033 | + | throw emitError(self, call.callee, ErrorKind::TypeMismatch(TypeMismatch { |
|
| 5034 | + | expected: Type::Unknown, |
|
| 5035 | + | actual: calleeTy, |
|
| 5036 | + | })); |
|
| 5035 | 5037 | }; |
|
| 5036 | 5038 | try checkCallArgs(self, node, call, info, ctx); |
|
| 5037 | 5039 | // Associate function type to callee. |
|
| 5038 | 5040 | setNodeType(self, call.callee, calleeTy); |
|
| 5039 | 5041 |
| 5152 | 5154 | } |
|
| 5153 | 5155 | ||
| 5154 | 5156 | /// Ensure slice range bounds are valid `u32` values. |
|
| 5155 | 5157 | fn checkSliceRangeIndices(self: *mut Resolver, range: ast::Range) throws (ResolveError) { |
|
| 5156 | 5158 | if let start = range.start { |
|
| 5157 | - | let _ = try checkAssignable(self, start, Type::U32); |
|
| 5159 | + | try checkIndex(self, start); |
|
| 5158 | 5160 | } |
|
| 5159 | 5161 | if let end = range.end { |
|
| 5160 | - | let _ = try checkAssignable(self, end, Type::U32); |
|
| 5162 | + | try checkIndex(self, end); |
|
| 5161 | 5163 | } |
|
| 5162 | 5164 | } |
|
| 5163 | 5165 | ||
| 5164 | 5166 | /// Emit an error when a slice range with compile-tyime values exceeds the array length. |
|
| 5165 | 5167 | fn validateArraySliceBounds(self: *mut Resolver, range: ast::Range, length: u32, site: *ast::Node) throws (ResolveError) { |
| 5187 | 5189 | throw emitError(self, site, ErrorKind::SliceRangeOutOfBounds); |
|
| 5188 | 5190 | } |
|
| 5189 | 5191 | } |
|
| 5190 | 5192 | } |
|
| 5191 | 5193 | ||
| 5194 | + | /// Check that an index expression has an unsigned integer type. |
|
| 5195 | + | /// Accepts `u8`, `u16`, `u32` and unsuffixed integer literals. |
|
| 5196 | + | /// Smaller types are widened to `u32` via a numeric cast coercion. |
|
| 5197 | + | fn checkIndex(self: *mut Resolver, indexNode: *ast::Node) throws (ResolveError) { |
|
| 5198 | + | let indexTy = try visit(self, indexNode, Type::U32); |
|
| 5199 | + | if indexTy == Type::Int or indexTy == Type::U32 { |
|
| 5200 | + | let _ = try expectAssignable(self, Type::U32, indexTy, indexNode); |
|
| 5201 | + | return; |
|
| 5202 | + | } |
|
| 5203 | + | match indexTy { |
|
| 5204 | + | case Type::U8, Type::U16 => { |
|
| 5205 | + | setNodeCoercion(self, indexNode, Coercion::NumericCast { |
|
| 5206 | + | from: indexTy, to: Type::U32, |
|
| 5207 | + | }); |
|
| 5208 | + | } |
|
| 5209 | + | else => { |
|
| 5210 | + | throw emitTypeMismatch(self, indexNode, TypeMismatch { |
|
| 5211 | + | expected: Type::U32, |
|
| 5212 | + | actual: indexTy, |
|
| 5213 | + | }); |
|
| 5214 | + | } |
|
| 5215 | + | } |
|
| 5216 | + | } |
|
| 5217 | + | ||
| 5192 | 5218 | /// Analyze an array or slice subscript expression. |
|
| 5193 | 5219 | fn resolveSubscript(self: *mut Resolver, node: *ast::Node, container: *ast::Node, indexNode: *ast::Node) -> Type |
|
| 5194 | 5220 | throws (ResolveError) |
|
| 5195 | 5221 | { |
|
| 5196 | 5222 | // Range subscripts always require `&` to form a slice. |
| 5199 | 5225 | let _ = try infer(self, container); |
|
| 5200 | 5226 | try checkSliceRangeIndices(self, range); |
|
| 5201 | 5227 | throw emitError(self, node, ErrorKind::SliceRequiresAddress); |
|
| 5202 | 5228 | } |
|
| 5203 | 5229 | let containerTy = try infer(self, container); |
|
| 5204 | - | let _ = try checkAssignable(self, indexNode, Type::U32); |
|
| 5230 | + | try checkIndex(self, indexNode); |
|
| 5205 | 5231 | let subjectTy = autoDeref(containerTy); |
|
| 5206 | 5232 | ||
| 5207 | 5233 | match subjectTy { |
|
| 5208 | 5234 | case Type::Array(arrayInfo) => { |
|
| 5209 | 5235 | return setNodeType(self, node, *arrayInfo.item); |
| 6233 | 6259 | } |
|
| 6234 | 6260 | ||
| 6235 | 6261 | /// Try to constant-fold a binary operation on two resolved operands. |
|
| 6236 | 6262 | /// Only folds when the result type is concrete. |
|
| 6237 | 6263 | fn tryFoldBinOp(self: *mut Resolver, node: *ast::Node, binop: ast::BinOp, resultTy: Type) { |
|
| 6238 | - | let leftVal = constValueFor(self, binop.left) |
|
| 6264 | + | let leftVal = constValueEntry(self, binop.left) |
|
| 6239 | 6265 | else return; |
|
| 6240 | - | let rightVal = constValueFor(self, binop.right) |
|
| 6266 | + | let rightVal = constValueEntry(self, binop.right) |
|
| 6241 | 6267 | else return; |
|
| 6242 | 6268 | ||
| 6243 | 6269 | // Fold integer binary ops. |
|
| 6244 | 6270 | if let case ConstValue::Int(leftInt) = leftVal { |
|
| 6245 | 6271 | if let case ConstValue::Int(rightInt) = rightVal { |
| 6371 | 6397 | let mut resultTy = Type::Unknown; |
|
| 6372 | 6398 | ||
| 6373 | 6399 | match unop.op { |
|
| 6374 | 6400 | case ast::UnaryOp::Not => { |
|
| 6375 | 6401 | resultTy = try checkBoolean(self, unop.value); |
|
| 6376 | - | if let value = constValueFor(self, unop.value) { |
|
| 6402 | + | if let value = constValueEntry(self, unop.value) { |
|
| 6377 | 6403 | if let case ConstValue::Bool(val) = value { |
|
| 6378 | 6404 | setNodeConstValue(self, node, ConstValue::Bool(not val)); |
|
| 6379 | 6405 | } |
|
| 6380 | 6406 | } |
|
| 6381 | 6407 | }, |
|
| 6382 | 6408 | case ast::UnaryOp::Neg => { |
|
| 6383 | 6409 | // TODO: Check that we're allowed to use `-` here? Should negation |
|
| 6384 | 6410 | // only be valid for signed integers? |
|
| 6385 | 6411 | resultTy = try checkNumeric(self, unop.value); |
|
| 6386 | - | if let value = constValueFor(self, unop.value) { |
|
| 6412 | + | if let value = constValueEntry(self, unop.value) { |
|
| 6387 | 6413 | // Get the constant expression for the value, flip the sign, |
|
| 6388 | 6414 | // and store that new expression on the unary op node. |
|
| 6389 | 6415 | if let case ConstValue::Int(intVal) = value { |
|
| 6390 | 6416 | setNodeConstValue( |
|
| 6391 | 6417 | self, |
| 6395 | 6421 | } |
|
| 6396 | 6422 | } |
|
| 6397 | 6423 | }, |
|
| 6398 | 6424 | case ast::UnaryOp::BitNot => { |
|
| 6399 | 6425 | resultTy = try checkNumeric(self, unop.value); |
|
| 6400 | - | if let value = constValueFor(self, unop.value) { |
|
| 6426 | + | if let value = constValueEntry(self, unop.value) { |
|
| 6401 | 6427 | if let case ConstValue::Int(intVal) = value { |
|
| 6402 | 6428 | let signed = constIntToSigned(intVal); |
|
| 6403 | 6429 | let inverted = constIntFromSigned(-(signed + 1), intVal.bits, intVal.signed); |
|
| 6404 | 6430 | setNodeConstValue(self, node, ConstValue::Int(inverted)); |
|
| 6405 | 6431 | } |
| 6539 | 6565 | ||
| 6540 | 6566 | /// Analyze a standalone expression by wrapping it in a synthetic function. |
|
| 6541 | 6567 | pub fn resolveExpr( |
|
| 6542 | 6568 | self: *mut Resolver, expr: *ast::Node, arena: *mut ast::NodeArena |
|
| 6543 | 6569 | ) -> Diagnostics throws (ResolveError) { |
|
| 6544 | - | let a = ast::nodeAllocator(arena); |
|
| 6570 | + | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 6545 | 6571 | let exprStmt = ast::synthNode(arena, ast::NodeValue::ExprStmt(expr)); |
|
| 6546 | 6572 | let bodyStmts = ast::nodeSlice(arena, 1).append(exprStmt, a); |
|
| 6547 | 6573 | let module = ast::synthFnModule(arena, ANALYZE_EXPR_FN_NAME, bodyStmts); |
|
| 6548 | 6574 | ||
| 6549 | 6575 | let case ast::NodeValue::Block(block) = module.modBody.value |
| 6696 | 6722 | } |
|
| 6697 | 6723 | // Phase 6: Resolve type bodies (record fields, union variants). |
|
| 6698 | 6724 | try resolveTypeBodies(res, block); |
|
| 6699 | 6725 | // Phase 7: Process all other declarations (statics, etc.). |
|
| 6700 | 6726 | for stmt in block.statements { |
|
| 6701 | - | // TODO: This error should propagate, since we catch errors in the `resolveModule` entry point. |
|
| 6702 | - | try visitDecl(res, stmt) catch { |
|
| 6703 | - | break; |
|
| 6704 | - | }; |
|
| 6727 | + | try visitDecl(res, stmt); |
|
| 6705 | 6728 | } |
|
| 6706 | 6729 | } |
|
| 6707 | 6730 | ||
| 6708 | 6731 | /// Analyze module definitions. This pass analyzes function bodies, recursing into sub-modules. |
|
| 6709 | 6732 | fn resolveModuleDefs(self: *mut Resolver, block: *ast::Block) throws (ResolveError) { |
lib/std/lang/resolver/tests.rad
+2 -2
| 419 | 419 | ||
| 420 | 420 | /// Verify that a node has a constant integer value with the expected magnitude. |
|
| 421 | 421 | fn expectConstInt(a: *super::Resolver, node: *ast::Node, expected: u32) |
|
| 422 | 422 | throws (testing::TestError) |
|
| 423 | 423 | { |
|
| 424 | - | let constVal = super::constValueFor(a, node) |
|
| 424 | + | let constVal = super::constValueEntry(a, node) |
|
| 425 | 425 | else throw testing::TestError::Failed; |
|
| 426 | 426 | ||
| 427 | 427 | let case super::ConstValue::Int(int) = constVal |
|
| 428 | 428 | else throw testing::TestError::Failed; |
|
| 429 | 429 |
| 986 | 986 | try expectNoErrors(&result); |
|
| 987 | 987 | ||
| 988 | 988 | let constStmt = try getBlockStmt(result.root, 1); |
|
| 989 | 989 | let case ast::NodeValue::ConstDecl(decl) = constStmt.value |
|
| 990 | 990 | else throw testing::TestError::Failed; |
|
| 991 | - | let valueConst = super::constValueFor(&a, decl.value) |
|
| 991 | + | let valueConst = super::constValueEntry(&a, decl.value) |
|
| 992 | 992 | else throw testing::TestError::Failed; |
|
| 993 | 993 | let case super::ConstValue::Int(lenVal) = valueConst |
|
| 994 | 994 | else throw testing::TestError::Failed; |
|
| 995 | 995 | try testing::expect(lenVal.magnitude == 3); |
|
| 996 | 996 | try testing::expect(not lenVal.negative); |
lib/std/sys/unix.rad
+2 -2
| 1 | 1 | //! Unix-specific system calls and utilities. |
|
| 2 | 2 | use std::intrinsics; |
|
| 3 | 3 | ||
| 4 | 4 | /// File access modes. |
|
| 5 | - | // TODO: Use unlabeled struct. |
|
| 5 | + | // TODO: Use unlabeled record (blocked on positional field access syntax). |
|
| 6 | 6 | pub record OpenFlags { |
|
| 7 | 7 | value: i64, |
|
| 8 | 8 | } |
|
| 9 | 9 | ||
| 10 | 10 | /// Open file for reading only. |
| 95 | 95 | return nil; |
|
| 96 | 96 | } |
|
| 97 | 97 | if n < 0 { |
|
| 98 | 98 | return nil; |
|
| 99 | 99 | } |
|
| 100 | - | return &buf[..(n as u32)]; |
|
| 100 | + | return &buf[..n as u32]; |
|
| 101 | 101 | } |
|
| 102 | 102 | ||
| 103 | 103 | /// Exit the current process with the given status code. |
|
| 104 | 104 | pub fn exit(status: i64) { |
|
| 105 | 105 | intrinsics::ecall(93, status, 0, 0, 0); |
test/runner.rad
+1 -2
| 118 | 118 | let len = sourcePath.len; |
|
| 119 | 119 | if len < 4 { |
|
| 120 | 120 | return nil; |
|
| 121 | 121 | } |
|
| 122 | 122 | // Check for .rad extension. |
|
| 123 | - | let extStart = len - 4; // TODO: language support for additions in ranges. |
|
| 124 | - | if not mem::eq(&sourcePath[extStart..len], ".rad") { |
|
| 123 | + | if not mem::eq(&sourcePath[len - 4..len], ".rad") { |
|
| 125 | 124 | return nil; |
|
| 126 | 125 | } |
|
| 127 | 126 | if len + 1 > buf.len { |
|
| 128 | 127 | return nil; |
|
| 129 | 128 | } |
test/tests/index.u8.rad
added
+32 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let arr: [i32; 4] = [10, 20, 42, 30]; |
|
| 5 | + | ||
| 6 | + | // u8 index. |
|
| 7 | + | let idx8: u8 = 2; |
|
| 8 | + | if arr[idx8] != 42 { |
|
| 9 | + | return 1; |
|
| 10 | + | } |
|
| 11 | + | // u16 index. |
|
| 12 | + | let idx16: u16 = 3; |
|
| 13 | + | if arr[idx16] != 30 { |
|
| 14 | + | return 2; |
|
| 15 | + | } |
|
| 16 | + | // u32 index (already worked). |
|
| 17 | + | let idx32: u32 = 0; |
|
| 18 | + | if arr[idx32] != 10 { |
|
| 19 | + | return 3; |
|
| 20 | + | } |
|
| 21 | + | // Unsuffixed integer literal index. |
|
| 22 | + | if arr[1] != 20 { |
|
| 23 | + | return 4; |
|
| 24 | + | } |
|
| 25 | + | // u8 index into slice. |
|
| 26 | + | let s = &arr[..]; |
|
| 27 | + | let si: u8 = 1; |
|
| 28 | + | if s[si] != 20 { |
|
| 29 | + | return 5; |
|
| 30 | + | } |
|
| 31 | + | return 0; |
|
| 32 | + | } |
test/tests/range.arithmetic.rad
added
+29 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let arr: [i32; 6] = [10, 20, 30, 40, 50, 60]; |
|
| 5 | + | let len: u32 = 6; |
|
| 6 | + | ||
| 7 | + | // Arithmetic in range start (no parens needed). |
|
| 8 | + | let s = &arr[len - 3..len]; |
|
| 9 | + | if s.len != 3 { return 1; } |
|
| 10 | + | if s[0] != 40 { return 2; } |
|
| 11 | + | if s[2] != 60 { return 3; } |
|
| 12 | + | ||
| 13 | + | // Arithmetic in range end. |
|
| 14 | + | let s2 = &arr[0..len - 2]; |
|
| 15 | + | if s2.len != 4 { return 4; } |
|
| 16 | + | if s2[3] != 40 { return 5; } |
|
| 17 | + | ||
| 18 | + | // Arithmetic in both. |
|
| 19 | + | let s3 = &arr[len - 4..len - 1]; |
|
| 20 | + | if s3.len != 3 { return 6; } |
|
| 21 | + | if s3[0] != 30 { return 7; } |
|
| 22 | + | if s3[2] != 50 { return 8; } |
|
| 23 | + | ||
| 24 | + | // Arithmetic in subscript. |
|
| 25 | + | if arr[len - 1] != 60 { return 9; } |
|
| 26 | + | if arr[len - 6] != 10 { return 10; } |
|
| 27 | + | ||
| 28 | + | return 0; |
|
| 29 | + | } |