feat(parser): reserved keyword as member name after .
After a leading `.` (enum literal `.enum`, field access `x.enum` /
`E.struct`, match arm `case .enum:`) a reserved keyword is unambiguously
the member/variant NAME — the dot rules out the keyword reading — so no
backtick escape is needed. A declaration of such a variant still needs
the backtick (enum { `enum: i64 }), since the decl site has no dot.
Adds Parser.dotMemberName() (identifier OR identifier-shaped keyword)
and routes the leading-dot enum-literal and postfix field-access sites
through it. readme updated. The reify example 0614 now uses the cleaner
reify(.enum(...)) spelling (still xfail — reify lands next commit).
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
E :: reify(.`enum(.{ variants = .[
|
||||
E :: reify(.enum(.{ variants = .[
|
||||
EnumVariant.{ name = "value", payload = i64 },
|
||||
EnumVariant.{ name = "closed", payload = void },
|
||||
] }));
|
||||
|
||||
18
readme.md
18
readme.md
@@ -184,7 +184,23 @@ positions are exempt**: a struct *field*, a union *tag*, and a protocol
|
||||
so they never mis-lower. The bare exemption covers only the identifier-classified
|
||||
reserved names (`i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`,
|
||||
`isize`, `Any`); `f32` and `f64` are lexer keywords, so even in a member slot they
|
||||
need the backtick (`` struct { `f32: i64 } ``). A leading backtick escapes one into
|
||||
need the backtick (`` struct { `f32: i64 } ``).
|
||||
|
||||
**After a leading `.`**, however, even a full lexer keyword is accepted bare as the
|
||||
member/variant name — the dot makes the keyword reading impossible, so no backtick
|
||||
is needed. This covers the enum-literal (`.enum`), field-access (`x.enum`,
|
||||
`E.struct`), and match-arm (`case .enum:`) positions. A *declaration* of such a
|
||||
variant still needs the backtick (`` enum { `enum: i64 } ``), since the decl site
|
||||
has no disambiguating dot:
|
||||
|
||||
```sx
|
||||
TI :: enum { `enum: i64; closed; } // decl: backtick needed (no dot)
|
||||
t := TI.enum(7); // construct: dot disambiguates — bare `enum`
|
||||
if t == { case .enum: (v) { … } // match: likewise bare after `.`
|
||||
case .closed: { … } }
|
||||
```
|
||||
|
||||
A leading backtick escapes one into
|
||||
a **raw identifier**:
|
||||
`` `name `` is the literal identifier `name` (the backtick drops out of the text),
|
||||
usable in **every** position — value, declaration, and type, and optional in the
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Token = @import("token.zig").Token;
|
||||
const Tag = @import("token.zig").Tag;
|
||||
const getKeyword = @import("token.zig").getKeyword;
|
||||
const Lexer = @import("lexer.zig").Lexer;
|
||||
const ast = @import("ast.zig");
|
||||
const Node = ast.Node;
|
||||
@@ -2568,16 +2569,16 @@ pub const Parser = struct {
|
||||
// Dereference: expr.*
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .deref_expr = .{ .operand = expr } });
|
||||
} else if (self.current.tag == .identifier) {
|
||||
// Named field access: expr.field
|
||||
const field = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
||||
} else if (self.current.tag == .int_literal) {
|
||||
// Numeric field access: tuple.0, tuple.1
|
||||
const field = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
||||
} else if (self.dotMemberName()) |field| {
|
||||
// Named field access: expr.field. A reserved keyword is a
|
||||
// valid member name here — the leading dot disambiguates
|
||||
// (`x.enum`, `E.struct`), so no backtick escape is needed.
|
||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
||||
} else {
|
||||
return self.fail("expected field name or index after '.'");
|
||||
}
|
||||
@@ -2960,12 +2961,11 @@ pub const Parser = struct {
|
||||
try self.expect(.r_bracket);
|
||||
return try self.createNode(start, .{ .array_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
|
||||
}
|
||||
// Enum literal: .variant_name
|
||||
if (self.current.tag != .identifier) {
|
||||
// Enum literal: .variant_name. A reserved keyword is a valid
|
||||
// variant name here — the leading dot disambiguates (`.enum`,
|
||||
// `.struct`), so no backtick escape is needed.
|
||||
const name = self.dotMemberName() orelse
|
||||
return self.fail("expected variant name, '{', or '[' after '.'");
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
// Enum literal: .variant_name — parsePostfix handles optional (...) as a call
|
||||
return try self.createNode(start, .{ .enum_literal = .{ .name = name } });
|
||||
},
|
||||
@@ -4106,6 +4106,22 @@ pub const Parser = struct {
|
||||
return self.source[token.loc.start..token.loc.end];
|
||||
}
|
||||
|
||||
/// After a `.` in member / enum-literal / variant position, a reserved
|
||||
/// keyword (`enum`, `struct`, `union`, `error`, …) is unambiguously the
|
||||
/// member NAME — the leading dot rules out the keyword reading, so no
|
||||
/// backtick escape is needed (`x.enum`, `.enum(p)`, `case .enum:`).
|
||||
/// Returns the token text and advances when `current` is an identifier OR
|
||||
/// an identifier-shaped keyword; null otherwise (a real syntax error there,
|
||||
/// left for the caller to report).
|
||||
fn dotMemberName(self: *Parser) ?[]const u8 {
|
||||
const txt = self.tokenSlice(self.current);
|
||||
if (self.current.tag == .identifier or getKeyword(txt) != null) {
|
||||
self.advance();
|
||||
return txt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn fail(self: *Parser, msg: []const u8) error{ParseError} {
|
||||
self.err_msg = msg;
|
||||
self.err_offset = self.current.loc.start;
|
||||
|
||||
Reference in New Issue
Block a user