feat: tuple syntax cutover — Tuple(...) type + .(...) value
Replace the bare-paren tuple grammar with explicit, position-unambiguous
forms, mirroring how structs work:
type `(A, B)` -> `Tuple(A, B)` (named keeps `:`)
value `(a, b)` -> `.(a, b)` (named uses `=`)
typed (new) -> `Tuple(A, B).(a, b)` (like `Point.{...}`)
failable `-> (T, !)` -> `-> T !`
`-> (T1, T2, !)`-> `-> Tuple(T1, T2) !` (channel outside Tuple)
Bare `(...)` is now grouping only, everywhere; a comma in bare parens is a
hard error with a migration hint. Grouping, function types `(A, B) -> R`,
param lists, lambdas, and match bindings are unaffected.
`Tuple(...)` is strictly a TYPE in every position (including `size_of` /
`type_info` args); a tuple VALUE comes only from `.(...)` (anonymous) or
`Tuple(...).(...)` (explicitly typed). A bare `Tuple(1, 2)` is a tuple
type with non-type elements -> rejected.
The ~110 tuple-bearing corpus files were migrated with a one-shot
AST-aware migrator (the `sx migrate` tool from the prior commit, removed
here). New examples: 0130 (new syntax), 0131 (typed construction), 1060
(named-tuple failable return). 1116 golden updated for the new hint text.
This commit is contained in:
@@ -321,7 +321,7 @@ write_object :: (obj: Object, sink: *Sink) -> !JsonError {
|
||||
// bytes written. Raises `error.Overflow` if `dst` is too small (the
|
||||
// partial contents of `dst` are then undefined — nothing is truncated
|
||||
// silently). No allocation.
|
||||
write_to_buffer :: (v: Value, dst: []u8) -> (i64, !JsonError) {
|
||||
write_to_buffer :: (v: Value, dst: []u8) -> i64 !JsonError {
|
||||
sink := Sink.{ dst = dst };
|
||||
try write_value(v, @sink);
|
||||
return sink.pos;
|
||||
@@ -386,7 +386,7 @@ JsonParseError :: error { UnexpectedToken, UnexpectedEnd, BadEscape, BadNumber,
|
||||
|
||||
// Lowercase/uppercase hex nibble value (0..15) of an ASCII byte; a non-hex
|
||||
// byte in a `\uXXXX` escape is a `BadEscape`.
|
||||
hex_value :: (c: u8) -> (i64, !JsonParseError) {
|
||||
hex_value :: (c: u8) -> i64 !JsonParseError {
|
||||
if c >= 48 and c <= 57 { return (cast(i64) c) - 48; } // '0'..'9'
|
||||
if c >= 97 and c <= 102 { return (cast(i64) c) - 97 + 10; } // 'a'..'f'
|
||||
if c >= 65 and c <= 70 { return (cast(i64) c) - 65 + 10; } // 'A'..'F'
|
||||
@@ -450,7 +450,7 @@ Parser :: struct {
|
||||
|
||||
// Read 4 hex digits at `i` (which must lie within [.., end)); returns
|
||||
// the 16-bit value. Fewer than 4 digits before `end` is a BadEscape.
|
||||
read_hex4 :: (self: *Parser, i: i64, end: i64) -> (i64, !JsonParseError) {
|
||||
read_hex4 :: (self: *Parser, i: i64, end: i64) -> i64 !JsonParseError {
|
||||
if i + 4 > end { raise error.BadEscape; }
|
||||
v := 0;
|
||||
k := 0;
|
||||
@@ -464,7 +464,7 @@ Parser :: struct {
|
||||
// Decode the escaped string body in [start, end) into `out`, returning
|
||||
// the decoded byte length. Pass 1 (in parse_string) guarantees there is
|
||||
// no dangling backslash, so the byte after every `\` is in range.
|
||||
decode_into :: (self: *Parser, start: i64, end: i64, out: [*]u8) -> (i64, !JsonParseError) {
|
||||
decode_into :: (self: *Parser, start: i64, end: i64, out: [*]u8) -> i64 !JsonParseError {
|
||||
di := 0;
|
||||
i := start;
|
||||
while i < end {
|
||||
@@ -511,7 +511,7 @@ Parser :: struct {
|
||||
// a zero-copy VIEW into `src` when the body has no escapes; otherwise
|
||||
// decodes into an `alloc`-ed buffer (bounded by the raw span). `pos`
|
||||
// ends just past the closing quote.
|
||||
parse_string :: (self: *Parser) -> (string, !JsonParseError) {
|
||||
parse_string :: (self: *Parser) -> string !JsonParseError {
|
||||
self.pos += 1; // consume opening quote
|
||||
start := self.pos;
|
||||
has_escape := false;
|
||||
@@ -547,7 +547,7 @@ Parser :: struct {
|
||||
// Parse an i64 integer (optional '-', then digits). Rejects leading
|
||||
// zeros, a fraction/exponent tail, and any value outside i64 — all
|
||||
// `BadNumber`. Accumulates in NEGATIVE space so i64 MIN parses exactly.
|
||||
parse_number :: (self: *Parser) -> (i64, !JsonParseError) {
|
||||
parse_number :: (self: *Parser) -> i64 !JsonParseError {
|
||||
// i64 bounds, built positionally because |MIN| is not a
|
||||
// representable positive i64 literal. `min_div10` is `MIN / 10`
|
||||
// truncated toward zero (remainder -8) — the digit loop's overflow
|
||||
@@ -585,7 +585,7 @@ Parser :: struct {
|
||||
}
|
||||
|
||||
// Parse an array starting at '['. Builds an `Array` through `alloc`.
|
||||
parse_array :: (self: *Parser) -> (Value, !JsonParseError) {
|
||||
parse_array :: (self: *Parser) -> Value !JsonParseError {
|
||||
self.pos += 1; // consume '['
|
||||
arr : Array = .{};
|
||||
self.skip_ws();
|
||||
@@ -609,7 +609,7 @@ Parser :: struct {
|
||||
|
||||
// Parse an object starting at '{'. Keys must be strings; insertion
|
||||
// order is preserved (duplicate keys are kept, never merged).
|
||||
parse_object :: (self: *Parser) -> (Value, !JsonParseError) {
|
||||
parse_object :: (self: *Parser) -> Value !JsonParseError {
|
||||
self.pos += 1; // consume '{'
|
||||
obj : Object = .{};
|
||||
self.skip_ws();
|
||||
@@ -640,7 +640,7 @@ Parser :: struct {
|
||||
}
|
||||
|
||||
// Parse any single value (after skipping leading whitespace).
|
||||
parse_value :: (self: *Parser) -> (Value, !JsonParseError) {
|
||||
parse_value :: (self: *Parser) -> Value !JsonParseError {
|
||||
self.skip_ws();
|
||||
if self.pos >= self.src.len { raise error.UnexpectedEnd; }
|
||||
c := self.src[self.pos];
|
||||
@@ -659,7 +659,7 @@ Parser :: struct {
|
||||
// `alloc` for composite nodes and decoded (escaped) strings. Un-escaped
|
||||
// string values are VIEWS into `src` and are valid only while `src` lives.
|
||||
// Trailing non-whitespace after the value raises `error.TrailingGarbage`.
|
||||
parse :: (src: string, alloc: Allocator) -> (Value, !JsonParseError) {
|
||||
parse :: (src: string, alloc: Allocator) -> Value !JsonParseError {
|
||||
p := Parser.{ src = src, alloc = alloc };
|
||||
v := try p.parse_value();
|
||||
p.skip_ws();
|
||||
|
||||
Reference in New Issue
Block a user