lang: Parse signs as unary operators
16d5efcb1fdfb531a448d42d8442f974fc9e495b992955f80fae2f1f252a890e
Previously, the scanner folded adjacent signs into numeric tokens, so expression tokenization depended on whitespace and subtraction could be misread as a signed literal. Now numeric literals contain only an unsigned magnitude, while the parser represents negation explicitly. This makes tokenization context-free and preserves literal range checking through unary expressions. Assisted-by: Codex:gpt-5.6-sol
1 parent
5579ed17
lib/std/arch/rv64/asm/parser.rad
+7 -2
| 825 | 825 | return value; |
|
| 826 | 826 | } |
|
| 827 | 827 | ||
| 828 | 828 | /// Parse integer literal text as an i64. |
|
| 829 | 829 | fn parseIntegerText(text: *[u8]) -> ?i64 { |
|
| 830 | - | let literal = try fmt::parseInt(text) catch { |
|
| 830 | + | if text.len == 0 { |
|
| 831 | + | return nil; |
|
| 832 | + | } |
|
| 833 | + | let negative = text[0] == '-'; |
|
| 834 | + | let magnitudeText = &text[1..] if negative or text[0] == '+' else text; |
|
| 835 | + | let literal = try fmt::parseInt(magnitudeText) catch { |
|
| 831 | 836 | return nil; |
|
| 832 | 837 | }; |
|
| 833 | - | if literal.negative { |
|
| 838 | + | if negative { |
|
| 834 | 839 | if literal.magnitude > parser::I64_MIN_MAGNITUDE { |
|
| 835 | 840 | return nil; |
|
| 836 | 841 | } |
|
| 837 | 842 | if literal.magnitude == parser::I64_MIN_MAGNITUDE { |
|
| 838 | 843 | return parser::I64_MIN; |
lib/std/fmt.rad
+3 -19
| 36 | 36 | ||
| 37 | 37 | /// Parsed integer literal metadata. |
|
| 38 | 38 | export record IntLiteral { |
|
| 39 | 39 | /// Raw characters that comprised the literal. |
|
| 40 | 40 | text: *[u8], |
|
| 41 | - | /// Absolute magnitude parsed from the literal. |
|
| 41 | + | /// Magnitude parsed from the literal. |
|
| 42 | 42 | magnitude: u64, |
|
| 43 | 43 | /// Radix used by the literal. |
|
| 44 | 44 | radix: Radix, |
|
| 45 | - | /// Whether the literal spelled an explicit sign. |
|
| 46 | - | signed: bool, |
|
| 47 | - | /// Whether the literal used a negative sign. |
|
| 48 | - | negative: bool, |
|
| 49 | 45 | } |
|
| 50 | 46 | ||
| 51 | 47 | /// Format a u32 by writing it to the provided buffer. |
|
| 52 | 48 | export fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] { |
|
| 53 | 49 | assert buffer.len >= U32_STR_LEN; |
| 208 | 204 | case '0' => return 0, |
|
| 209 | 205 | else => return ch, |
|
| 210 | 206 | } |
|
| 211 | 207 | } |
|
| 212 | 208 | ||
| 213 | - | /// Parse an integer literal (binary, decimal, or hexadecimal) including an optional sign. |
|
| 209 | + | /// Parse an unsigned integer literal (binary, decimal, or hexadecimal). |
|
| 214 | 210 | export fn parseInt(text: *[u8]) -> IntLiteral throws (ParseError) { |
|
| 215 | 211 | if text.len == 0 { |
|
| 216 | 212 | throw ParseError::Invalid; |
|
| 217 | 213 | } |
|
| 218 | - | let first = text[0]; |
|
| 219 | - | let negative = first == '-'; |
|
| 220 | - | let signed: bool = negative or (first == '+'); |
|
| 221 | 214 | ||
| 222 | 215 | let mut start: u32 = 0; |
|
| 223 | 216 | let mut radix: u32 = 10; |
|
| 224 | 217 | let mut radixType = Radix::Decimal; |
|
| 225 | - | ||
| 226 | - | if signed { |
|
| 227 | - | set start = 1; |
|
| 228 | - | if start >= text.len { |
|
| 229 | - | throw ParseError::Invalid; |
|
| 230 | - | } |
|
| 231 | - | } |
|
| 232 | 218 | if start + 1 < text.len and text[start] == '0' { |
|
| 233 | 219 | let prefix = text[start + 1]; |
|
| 234 | 220 | if prefix == 'x' or prefix == 'X' { |
|
| 235 | 221 | set radix = 16; |
|
| 236 | 222 | set radixType = Radix::Hex; |
| 259 | 245 | if value > U64_MAX - (digit as u64) { |
|
| 260 | 246 | throw ParseError::Overflow; |
|
| 261 | 247 | } |
|
| 262 | 248 | set value += (digit as u64); |
|
| 263 | 249 | } |
|
| 264 | - | return IntLiteral { |
|
| 265 | - | text, magnitude: value, radix: radixType, signed, negative, |
|
| 266 | - | }; |
|
| 250 | + | return IntLiteral { text, magnitude: value, radix: radixType }; |
|
| 267 | 251 | } |
|
| 268 | 252 | ||
| 269 | 253 | /// Process escape sequences in a raw string, writing the result into `dst`. |
|
| 270 | 254 | /// Returns the number of bytes written. |
|
| 271 | 255 | export fn unescapeString(raw: *[u8], dst: *mut [u8]) -> u32 { |
lib/std/lang/lower.rad
+6 -2
| 6026 | 6026 | return val; |
|
| 6027 | 6027 | } |
|
| 6028 | 6028 | ||
| 6029 | 6029 | /// Lower a unary operation. |
|
| 6030 | 6030 | fn lowerUnOp(self: *mut FnLowerer, node: *ast::Node, unop: ast::UnOp) -> il::Val throws (LowerError) { |
|
| 6031 | + | if unop.op == ast::UnaryOp::Neg { |
|
| 6032 | + | if let case ast::NodeValue::Number(lit) = unop.value.value { |
|
| 6033 | + | return il::Val::Imm((0 - lit.magnitude) as i64); |
|
| 6034 | + | } |
|
| 6035 | + | } |
|
| 6031 | 6036 | let val = try lowerExpr(self, unop.value); |
|
| 6032 | 6037 | let t = try typeOf(self, node); |
|
| 6033 | 6038 | let typ = ilType(self.low, t); |
|
| 6034 | 6039 | let dst = nextReg(self); |
|
| 6035 | 6040 | let mut needsExt: bool = false; |
| 7065 | 7070 | } |
|
| 7066 | 7071 | case ast::NodeValue::ScopeAccess(_) => { |
|
| 7067 | 7072 | set val = try lowerScopeAccess(self, node); |
|
| 7068 | 7073 | } |
|
| 7069 | 7074 | case ast::NodeValue::Number(lit) => { |
|
| 7070 | - | let mag = -(lit.magnitude as i64) if lit.negative else lit.magnitude as i64; |
|
| 7071 | - | set val = il::Val::Imm(mag); |
|
| 7075 | + | set val = il::Val::Imm(lit.magnitude as i64); |
|
| 7072 | 7076 | } |
|
| 7073 | 7077 | case ast::NodeValue::Bool(b) => { |
|
| 7074 | 7078 | set val = il::Val::Imm(1) if b else il::Val::Imm(0); |
|
| 7075 | 7079 | } |
|
| 7076 | 7080 | case ast::NodeValue::Char(c) => { |
lib/std/lang/parser.rad
+1 -1
| 1322 | 1322 | { |
|
| 1323 | 1323 | try expect(p, scanner::TokenKind::Try, "expected `try`"); |
|
| 1324 | 1324 | ||
| 1325 | 1325 | let shouldPanic = consume(p, scanner::TokenKind::Bang); |
|
| 1326 | 1326 | let returnsOptional = consume(p, scanner::TokenKind::Question); |
|
| 1327 | - | let expr = try parsePrimary(p); |
|
| 1327 | + | let expr = try parseUnaryExpr(p); |
|
| 1328 | 1328 | let mut catches = ast::nodeSlice(p.arena, 4); |
|
| 1329 | 1329 | ||
| 1330 | 1330 | while consume(p, scanner::TokenKind::Catch) { |
|
| 1331 | 1331 | let mut binding: ?*ast::Node = nil; |
|
| 1332 | 1332 | let mut typeNode: ?*ast::Node = nil; |
lib/std/lang/parser/tests.rad
+27 -34
| 279 | 279 | let lit = try parseNumberLiteral("1234"); |
|
| 280 | 280 | try testing::expect(lit.magnitude == 1234); |
|
| 281 | 281 | try testing::expect(lit.radix == fmt::Radix::Decimal); |
|
| 282 | 282 | } |
|
| 283 | 283 | ||
| 284 | - | /// Verify that hexadecimal literals record metadata without marking them signed. |
|
| 284 | + | /// Verify that hexadecimal literals record magnitude and radix metadata. |
|
| 285 | 285 | @test fn testParseNumberMetadata() throws (testing::TestError) { |
|
| 286 | 286 | let lit = try parseNumberLiteral("0xFF"); |
|
| 287 | 287 | try testing::expect(lit.magnitude == 0xFF); |
|
| 288 | 288 | try testing::expect(lit.radix == fmt::Radix::Hex); |
|
| 289 | - | try testing::expect(not lit.signed); |
|
| 290 | - | try testing::expect(not lit.negative); |
|
| 291 | 289 | } |
|
| 292 | 290 | ||
| 293 | 291 | /// Verify that binary literals capture their radix. |
|
| 294 | 292 | @test fn testParseBinaryLiteralMetadata() throws (testing::TestError) { |
|
| 295 | 293 | let lit = try parseNumberLiteral("0b1010"); |
|
| 296 | 294 | try testing::expect(lit.magnitude == 0b1010); |
|
| 297 | 295 | try testing::expect(lit.radix == fmt::Radix::Binary); |
|
| 298 | 296 | } |
|
| 299 | 297 | ||
| 300 | - | /// Signed literals produced by the scanner keep sign details in metadata. |
|
| 301 | - | @test fn testParseSignedLiteralMetadata() throws (testing::TestError) { |
|
| 302 | - | let literal = try parseNumberLiteral("42"); |
|
| 303 | - | try testing::expect(not literal.signed); |
|
| 304 | - | try testing::expect(not literal.negative); |
|
| 305 | - | try testing::expect(literal.magnitude == 42); |
|
| 306 | - | ||
| 307 | - | let neg = try parseNumberLiteral("-99"); |
|
| 308 | - | try testing::expect(neg.signed); |
|
| 309 | - | try testing::expect(neg.negative); |
|
| 310 | - | try testing::expect(neg.magnitude == 99); |
|
| 298 | + | /// Negative literals parse as unary negation of an unsigned number. |
|
| 299 | + | @test fn testParseNegativeLiteral() throws (testing::TestError) { |
|
| 300 | + | let node = try! parseExprStr("-99"); |
|
| 301 | + | let case ast::NodeValue::UnOp(neg) = node.value |
|
| 302 | + | else throw testing::TestError::Failed; |
|
| 303 | + | try testing::expect(neg.op == ast::UnaryOp::Neg); |
|
| 304 | + | let case ast::NodeValue::Number(lit) = neg.value.value |
|
| 305 | + | else throw testing::TestError::Failed; |
|
| 306 | + | try testing::expect(lit.magnitude == 99); |
|
| 311 | 307 | } |
|
| 312 | 308 | ||
| 313 | - | /// Literals with prefixes still parse correctly when explicitly signed. |
|
| 314 | - | @test fn testParseSignedPrefixedLiteral() throws (testing::TestError) { |
|
| 315 | - | let hex = try parseNumberLiteral("+0x2A"); |
|
| 316 | - | try testing::expect(hex.signed); |
|
| 317 | - | try testing::expect(not hex.negative); |
|
| 318 | - | try testing::expect(hex.radix == fmt::Radix::Hex); |
|
| 319 | - | try testing::expect(hex.magnitude == 0x2A); |
|
| 320 | - | ||
| 321 | - | let neg = try parseNumberLiteral("-0x2A"); |
|
| 322 | - | try testing::expect(neg.signed); |
|
| 323 | - | try testing::expect(neg.negative); |
|
| 324 | - | try testing::expect(neg.radix == fmt::Radix::Hex); |
|
| 325 | - | try testing::expect(neg.magnitude == 0x2A); |
|
| 326 | - | ||
| 327 | - | let bin = try parseNumberLiteral("-0b11"); |
|
| 328 | - | try testing::expect(bin.signed); |
|
| 329 | - | try testing::expect(bin.negative); |
|
| 330 | - | try testing::expect(bin.radix == fmt::Radix::Binary); |
|
| 331 | - | try testing::expect(bin.magnitude == 0b11); |
|
| 309 | + | /// Unary plus is not part of the expression grammar. |
|
| 310 | + | @test fn testRejectUnaryPlus() throws (testing::TestError) { |
|
| 311 | + | try expectNumberLiteralFail("+1"); |
|
| 312 | + | try expectNumberLiteralFail("+value"); |
|
| 313 | + | try expectNumberLiteralFail("+(value)"); |
|
| 332 | 314 | } |
|
| 333 | 315 | ||
| 334 | 316 | /// Range expressions parse with explicit start and end bounds. |
|
| 335 | 317 | @test fn testParseRangeExpr() throws (testing::TestError) { |
|
| 336 | 318 | let node = try! parseExprStr("0..5"); |
| 354 | 336 | // 2^64 overflows u64. |
|
| 355 | 337 | try expectNumberLiteralFail("18446744073709551616"); |
|
| 356 | 338 | try expectNumberLiteralFail("0x10000000000000000"); |
|
| 357 | 339 | try expectNumberLiteralFail("0x1G"); |
|
| 358 | 340 | try expectNumberLiteralFail("0b102"); |
|
| 359 | - | try expectNumberLiteralFail("+0x1G"); |
|
| 360 | 341 | } |
|
| 361 | 342 | ||
| 362 | 343 | /// Test parsing nil literal. |
|
| 363 | 344 | @test fn testParseNil() throws (testing::TestError) { |
|
| 364 | 345 | let r1 = try! parseExprStr("nil"); |
| 1416 | 1397 | try expectIdent(node.expr, "value"); |
|
| 1417 | 1398 | try testing::expect(node.catches.len == 0); |
|
| 1418 | 1399 | try testing::expect(not node.shouldPanic); |
|
| 1419 | 1400 | } |
|
| 1420 | 1401 | ||
| 1402 | + | /// Test that `try?` consumes a unary operand. |
|
| 1403 | + | @test fn testParseTryOptionalUnary() throws (testing::TestError) { |
|
| 1404 | + | let root = try! parseExprStr("try? -value"); |
|
| 1405 | + | let case ast::NodeValue::Try(node) = root.value |
|
| 1406 | + | else throw testing::TestError::Failed; |
|
| 1407 | + | let case ast::NodeValue::UnOp(neg) = node.expr.value |
|
| 1408 | + | else throw testing::TestError::Failed; |
|
| 1409 | + | try testing::expect(neg.op == ast::UnaryOp::Neg); |
|
| 1410 | + | try expectIdent(neg.value, "value"); |
|
| 1411 | + | try testing::expect(node.returnsOptional); |
|
| 1412 | + | } |
|
| 1413 | + | ||
| 1421 | 1414 | /// Test parsing a `try!` expression that panics on error. |
|
| 1422 | 1415 | @test fn testParseTryBang() throws (testing::TestError) { |
|
| 1423 | 1416 | let root = try! parseExprStr("try! value"); |
|
| 1424 | 1417 | let case ast::NodeValue::Try(node) = root.value |
|
| 1425 | 1418 | else throw testing::TestError::Failed; |
lib/std/lang/resolver.rad
+14 -8
| 1790 | 1790 | // For unsuffixed integer expressions (`Type::Int`), only |
|
| 1791 | 1791 | // validate literals directly written by the programmer. |
|
| 1792 | 1792 | // Folded results (e.g. `0 - 65`) may not fit the target |
|
| 1793 | 1793 | // type but are valid wrapping arithmetic at runtime. |
|
| 1794 | 1794 | if let value = constValueEntry(self, rval) { |
|
| 1795 | - | if from <> Type::Int or isNumberLiteral(rval) { |
|
| 1795 | + | if from <> Type::Int or isIntegerLiteralExpr(rval) { |
|
| 1796 | 1796 | if validateConstIntRange(value, to) { |
|
| 1797 | 1797 | return Coercion::Identity; |
|
| 1798 | 1798 | } |
|
| 1799 | 1799 | return nil; |
|
| 1800 | 1800 | } |
| 2673 | 2673 | }, |
|
| 2674 | 2674 | case ast::NodeValue::Number(lit) => { |
|
| 2675 | 2675 | setNodeConstValue(self, node, ConstValue::Int(ConstInt { |
|
| 2676 | 2676 | magnitude: lit.magnitude, |
|
| 2677 | 2677 | bits: 64, |
|
| 2678 | - | signed: lit.signed, |
|
| 2679 | - | negative: lit.negative, |
|
| 2678 | + | signed: false, |
|
| 2679 | + | negative: false, |
|
| 2680 | 2680 | })); |
|
| 2681 | 2681 | return setNodeType(self, node, Type::Int); |
|
| 2682 | 2682 | }, |
|
| 2683 | 2683 | case ast::NodeValue::Placeholder => { |
|
| 2684 | 2684 | return setNodeType(self, node, hint); |
| 2798 | 2798 | setNodeType(self, decl.value, bindingTy); |
|
| 2799 | 2799 | ||
| 2800 | 2800 | return Type::Void; |
|
| 2801 | 2801 | } |
|
| 2802 | 2802 | ||
| 2803 | - | /// Check whether a node is a number literal. |
|
| 2804 | - | fn isNumberLiteral(node: *ast::Node) -> bool { |
|
| 2805 | - | if let case ast::NodeValue::Number(_) = node.value { |
|
| 2806 | - | return true; |
|
| 2803 | + | /// Check whether a node is an integer literal, optionally under unary negation. |
|
| 2804 | + | fn isIntegerLiteralExpr(node: *ast::Node) -> bool { |
|
| 2805 | + | match node.value { |
|
| 2806 | + | case ast::NodeValue::Number(_) => return true, |
|
| 2807 | + | case ast::NodeValue::UnOp(unop) => { |
|
| 2808 | + | if unop.op == ast::UnaryOp::Neg { |
|
| 2809 | + | return isIntegerLiteralExpr(unop.value); |
|
| 2810 | + | } |
|
| 2811 | + | return false; |
|
| 2812 | + | }, |
|
| 2813 | + | else => return false, |
|
| 2807 | 2814 | } |
|
| 2808 | - | return false; |
|
| 2809 | 2815 | } |
|
| 2810 | 2816 | ||
| 2811 | 2817 | /// Determine whether a node represents a compile-time constant expression. |
|
| 2812 | 2818 | export fn isConstExpr(self: *Resolver, node: *ast::Node) -> bool { |
|
| 2813 | 2819 | match node.value { |
lib/std/lang/resolver/tests.rad
+4 -0
| 1790 | 1790 | try expectAnalyzeOk("let x: i16 = -32768;"); |
|
| 1791 | 1791 | try expectAnalyzeOk("let x: u16 = 0xFFFF;"); |
|
| 1792 | 1792 | try expectAnalyzeOk("let x: i32 = 2147483647;"); |
|
| 1793 | 1793 | try expectAnalyzeOk("let x: i32 = -2147483648;"); |
|
| 1794 | 1794 | try expectAnalyzeOk("let x: u32 = 0xFFFFFFFF;"); |
|
| 1795 | + | try expectAnalyzeOk("let x: i64 = 9223372036854775807;"); |
|
| 1796 | + | try expectAnalyzeOk("let x: i64 = -9223372036854775808;"); |
|
| 1795 | 1797 | ||
| 1796 | 1798 | try expectAnalyzeOk("constant LIMIT: u8 = 0xFF;"); |
|
| 1797 | 1799 | ||
| 1798 | 1800 | try expectIntMismatch("let x: i8 = 128;", super::Type::I8); |
|
| 1799 | 1801 | try expectIntMismatch("let x: i8 = -129;", super::Type::I8); |
| 1809 | 1811 | try expectIntMismatch("let x: i32 = 2147483648;", super::Type::I32); |
|
| 1810 | 1812 | try expectIntMismatch("let x: i32 = -2147483649;", super::Type::I32); |
|
| 1811 | 1813 | try expectIntMismatch("let x: i32 = 0xFFFFFFFF;", super::Type::I32); |
|
| 1812 | 1814 | try expectIntMismatch("let x: u32 = -1;", super::Type::U32); |
|
| 1813 | 1815 | try expectIntMismatch("let x: u32 = 0x100000000;", super::Type::U32); |
|
| 1816 | + | try expectIntMismatch("let x: i64 = 9223372036854775808;", super::Type::I64); |
|
| 1817 | + | try expectIntMismatch("let x: i64 = -9223372036854775809;", super::Type::I64); |
|
| 1814 | 1818 | try expectIntMismatch("constant LIMIT: u8 = 512;", super::Type::U8); |
|
| 1815 | 1819 | try expectIntMismatch("constant LIMIT: u8 = -5;", super::Type::U8); |
|
| 1816 | 1820 | } |
|
| 1817 | 1821 | ||
| 1818 | 1822 | @test fn testNilCoercions() throws (testing::TestError) { |
lib/std/lang/scanner.rad
+0 -11
| 301 | 301 | } |
|
| 302 | 302 | } |
|
| 303 | 303 | ||
| 304 | 304 | /// Scan numeric literal (decimal, hex, or binary). |
|
| 305 | 305 | fn scanNumber(s: *mut Scanner) -> Token { |
|
| 306 | - | let first = s.source[s.cursor - 1]; |
|
| 307 | - | if first == '-' or first == '+' { |
|
| 308 | - | advance(s); |
|
| 309 | - | } |
|
| 310 | 306 | // Check for hex literal (`0x` or `0X` prefix). |
|
| 311 | 307 | if s.source[s.cursor - 1] == '0' { |
|
| 312 | 308 | if let ch = current(s); ch == 'x' or ch == 'X' { |
|
| 313 | 309 | advance(s); |
|
| 314 | 310 | // Must have at least one hex digit after `0x`. |
| 461 | 457 | return tok(s, TokenKind::Arrow); |
|
| 462 | 458 | } |
|
| 463 | 459 | if consume(s, '=') { |
|
| 464 | 460 | return tok(s, TokenKind::MinusEqual); |
|
| 465 | 461 | } |
|
| 466 | - | // If followed by a digit, scan as negative number |
|
| 467 | - | if let ch = current(s); char::isDigit(ch) { |
|
| 468 | - | return scanNumber(s); |
|
| 469 | - | } |
|
| 470 | 462 | return tok(s, TokenKind::Minus); |
|
| 471 | 463 | } |
|
| 472 | 464 | case '+' => { |
|
| 473 | 465 | if consume(s, '=') { |
|
| 474 | 466 | return tok(s, TokenKind::PlusEqual); |
|
| 475 | 467 | } |
|
| 476 | - | if let ch = current(s); char::isDigit(ch) { |
|
| 477 | - | return scanNumber(s); |
|
| 478 | - | } |
|
| 479 | 468 | return tok(s, TokenKind::Plus); |
|
| 480 | 469 | } |
|
| 481 | 470 | case '/' => { |
|
| 482 | 471 | if consume(s, '=') { |
|
| 483 | 472 | return tok(s, TokenKind::SlashEqual); |
lib/std/lang/scanner/tests.rad
+20 -24
| 198 | 198 | try testing::expect(tok.source.len == 7); |
|
| 199 | 199 | } |
|
| 200 | 200 | ||
| 201 | 201 | @test fn testScanNumbers() throws (testing::TestError) { |
|
| 202 | 202 | let mut s = testScanner( |
|
| 203 | - | "9841029 45.67 -128 +0x2A +0b11" |
|
| 203 | + | "2 -2 +2 value-2 value+2 value--2 value+-2 value-+2 f(-2)" |
|
| 204 | 204 | ); |
|
| 205 | - | let mut tok: super::Token = super::next(&mut s); |
|
| 206 | - | try testing::expect(tok.kind == super::TokenKind::Number); |
|
| 207 | - | try testing::expect(mem::eq(tok.source, "9841029")); |
|
| 208 | - | try testing::expect(tok.source.len == 7); |
|
| 209 | - | ||
| 210 | - | set tok = super::next(&mut s); |
|
| 211 | - | try testing::expect(tok.kind == super::TokenKind::Number); |
|
| 212 | - | try testing::expect(tok.source.len == 5); |
|
| 213 | - | ||
| 214 | - | set tok = super::next(&mut s); |
|
| 215 | - | try testing::expect(tok.kind == super::TokenKind::Number); |
|
| 216 | - | try testing::expect(mem::eq(tok.source, "-128")); |
|
| 217 | - | try testing::expect(tok.source.len == 4); |
|
| 218 | - | ||
| 219 | - | set tok = super::next(&mut s); |
|
| 220 | - | try testing::expect(tok.kind == super::TokenKind::Number); |
|
| 221 | - | try testing::expect(mem::eq(tok.source, "+0x2A")); |
|
| 222 | - | try testing::expect(tok.source.len == 5); |
|
| 223 | - | ||
| 224 | - | set tok = super::next(&mut s); |
|
| 225 | - | try testing::expect(tok.kind == super::TokenKind::Number); |
|
| 226 | - | try testing::expect(mem::eq(tok.source, "+0b11")); |
|
| 227 | - | try testing::expect(tok.source.len == 5); |
|
| 205 | + | let expected: [super::TokenKind; 29] = [ |
|
| 206 | + | super::TokenKind::Number, |
|
| 207 | + | super::TokenKind::Minus, super::TokenKind::Number, |
|
| 208 | + | super::TokenKind::Plus, super::TokenKind::Number, |
|
| 209 | + | super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Number, |
|
| 210 | + | super::TokenKind::Ident, super::TokenKind::Plus, super::TokenKind::Number, |
|
| 211 | + | super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Minus, |
|
| 212 | + | super::TokenKind::Number, |
|
| 213 | + | super::TokenKind::Ident, super::TokenKind::Plus, super::TokenKind::Minus, |
|
| 214 | + | super::TokenKind::Number, |
|
| 215 | + | super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Plus, |
|
| 216 | + | super::TokenKind::Number, |
|
| 217 | + | super::TokenKind::Ident, super::TokenKind::LParen, super::TokenKind::Minus, |
|
| 218 | + | super::TokenKind::Number, super::TokenKind::RParen, |
|
| 219 | + | super::TokenKind::Eof, |
|
| 220 | + | ]; |
|
| 221 | + | for expectedKind in expected { |
|
| 222 | + | try testing::expect(super::next(&mut s).kind == expectedKind); |
|
| 223 | + | } |
|
| 228 | 224 | } |
|
| 229 | 225 | ||
| 230 | 226 | @test fn testScanKeywords() throws (testing::TestError) { |
|
| 231 | 227 | let mut s = testScanner("nil mod not static"); |
|
| 232 | 228 | let tok1: super::Token = super::next(&mut s); |
lib/std/tests.rad
+11 -8
| 110 | 110 | let dec = try fmt::parseInt("123") catch { |
|
| 111 | 111 | throw testing::TestError::Failed; |
|
| 112 | 112 | }; |
|
| 113 | 113 | try testing::expect(dec.magnitude == 123); |
|
| 114 | 114 | try testing::expect(dec.radix == fmt::Radix::Decimal); |
|
| 115 | - | try testing::expect(not dec.signed); |
|
| 116 | - | try testing::expect(not dec.negative); |
|
| 117 | 115 | ||
| 118 | - | let hex = try fmt::parseInt("-0x2a") catch { |
|
| 116 | + | let hex = try fmt::parseInt("0x2a") catch { |
|
| 119 | 117 | throw testing::TestError::Failed; |
|
| 120 | 118 | }; |
|
| 121 | 119 | try testing::expect(hex.magnitude == 42); |
|
| 122 | 120 | try testing::expect(hex.radix == fmt::Radix::Hex); |
|
| 123 | - | try testing::expect(hex.signed); |
|
| 124 | - | try testing::expect(hex.negative); |
|
| 125 | 121 | ||
| 126 | - | let bin = try fmt::parseInt("+0b101") catch { |
|
| 122 | + | let bin = try fmt::parseInt("0b101") catch { |
|
| 127 | 123 | throw testing::TestError::Failed; |
|
| 128 | 124 | }; |
|
| 129 | 125 | try testing::expect(bin.magnitude == 5); |
|
| 130 | 126 | try testing::expect(bin.radix == fmt::Radix::Binary); |
|
| 131 | - | try testing::expect(bin.signed); |
|
| 132 | - | try testing::expect(not bin.negative); |
|
| 133 | 127 | } |
|
| 134 | 128 | ||
| 135 | 129 | @test fn testDigitFromAscii() throws (testing::TestError) { |
|
| 136 | 130 | let zero = fmt::digitFromAscii('0', 10) else throw testing::TestError::Failed; |
|
| 137 | 131 | try testing::expect(zero == 0); |
| 160 | 154 | return; |
|
| 161 | 155 | }; |
|
| 162 | 156 | throw testing::TestError::Failed; |
|
| 163 | 157 | } |
|
| 164 | 158 | ||
| 159 | + | @test fn testParseIntLiteralTextRejectsSigns() throws (testing::TestError) { |
|
| 160 | + | try fmt::parseInt("-1") catch { |
|
| 161 | + | try fmt::parseInt("+1") catch { |
|
| 162 | + | return; |
|
| 163 | + | }; |
|
| 164 | + | }; |
|
| 165 | + | throw testing::TestError::Failed; |
|
| 166 | + | } |
|
| 167 | + | ||
| 165 | 168 | @test fn testParseIntLiteralTextInvalidDigitErrors() throws (testing::TestError) { |
|
| 166 | 169 | try fmt::parseInt("0b2") catch { |
|
| 167 | 170 | return; |
|
| 168 | 171 | }; |
|
| 169 | 172 | throw testing::TestError::Failed; |
test/tests/arith.subword.ril
+0 -1
| 242 | 242 | @assert.fail29 |
|
| 243 | 243 | unreachable; |
|
| 244 | 244 | @assert.ok30 |
|
| 245 | 245 | ret 0; |
|
| 246 | 246 | } |
|
| 247 | - |
test/tests/arith.w64.ril
+0 -1
| 293 | 293 | @assert.fail35 |
|
| 294 | 294 | unreachable; |
|
| 295 | 295 | @assert.ok36 |
|
| 296 | 296 | ret 0; |
|
| 297 | 297 | } |
|
| 298 | - |
test/tests/cast.same.size.ril
+0 -1
| 46 | 46 | @assert.fail19 |
|
| 47 | 47 | unreachable; |
|
| 48 | 48 | @assert.ok20 |
|
| 49 | 49 | ret 0; |
|
| 50 | 50 | } |
|
| 51 | - |
test/tests/match.nested.iflet.ril
+0 -1
| 114 | 114 | @assert.fail13 |
|
| 115 | 115 | unreachable; |
|
| 116 | 116 | @assert.ok14 |
|
| 117 | 117 | ret 0; |
|
| 118 | 118 | } |
|
| 119 | - |
test/tests/match.nested.pattern.ril
+0 -1
| 406 | 406 | @assert.fail37 |
|
| 407 | 407 | unreachable; |
|
| 408 | 408 | @assert.ok38 |
|
| 409 | 409 | ret 0; |
|
| 410 | 410 | } |
|
| 411 | - |
test/tests/match.nested.record.ril
+0 -1
| 174 | 174 | @assert.fail17 |
|
| 175 | 175 | unreachable; |
|
| 176 | 176 | @assert.ok18 |
|
| 177 | 177 | ret 0; |
|
| 178 | 178 | } |
|
| 179 | - |
test/tests/match.nested.union.ril
+0 -1
| 222 | 222 | @assert.fail23 |
|
| 223 | 223 | unreachable; |
|
| 224 | 224 | @assert.ok24 |
|
| 225 | 225 | ret 0; |
|
| 226 | 226 | } |
|
| 227 | - |
test/tests/unop.rad
+10 -0
| 1 | 1 | /// Returns the arithmetic negation of its argument. |
|
| 2 | 2 | fn neg(x: i32) -> i32 { |
|
| 3 | 3 | return -x; |
|
| 4 | 4 | } |
|
| 5 | 5 | ||
| 6 | + | /// Subtracts a literal without requiring whitespace before the operator. |
|
| 7 | + | fn subtractAdjacent(value: i32) -> i32 { |
|
| 8 | + | return value-2; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Returns a negated integer literal. |
|
| 12 | + | fn negLiteral() -> i32 { |
|
| 13 | + | return -42; |
|
| 14 | + | } |
|
| 15 | + | ||
| 6 | 16 | /// Returns the arithmetic negation of an i8. |
|
| 7 | 17 | fn negI8(x: i8) -> i8 { |
|
| 8 | 18 | return -x; |
|
| 9 | 19 | } |
|
| 10 | 20 |
test/tests/unop.ril
+11 -0
| 2 | 2 | @entry0 |
|
| 3 | 3 | neg w32 %1 %0; |
|
| 4 | 4 | ret %1; |
|
| 5 | 5 | } |
|
| 6 | 6 | ||
| 7 | + | fn w32 $subtractAdjacent(w32 %0) { |
|
| 8 | + | @entry0 |
|
| 9 | + | sub w32 %1 %0 2; |
|
| 10 | + | ret %1; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn w32 $negLiteral() { |
|
| 14 | + | @entry0 |
|
| 15 | + | ret -42; |
|
| 16 | + | } |
|
| 17 | + | ||
| 7 | 18 | fn w8 $negI8(w8 %0) { |
|
| 8 | 19 | @entry0 |
|
| 9 | 20 | neg w8 %1 %0; |
|
| 10 | 21 | sext w8 %2 %1; |
|
| 11 | 22 | ret %2; |