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.sx";
|
||||||
#import "modules/std/meta.sx";
|
#import "modules/std/meta.sx";
|
||||||
|
|
||||||
E :: reify(.`enum(.{ variants = .[
|
E :: reify(.enum(.{ variants = .[
|
||||||
EnumVariant.{ name = "value", payload = i64 },
|
EnumVariant.{ name = "value", payload = i64 },
|
||||||
EnumVariant.{ name = "closed", payload = void },
|
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
|
so they never mis-lower. The bare exemption covers only the identifier-classified
|
||||||
reserved names (`i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`,
|
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
|
`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**:
|
a **raw identifier**:
|
||||||
`` `name `` is the literal identifier `name` (the backtick drops out of the text),
|
`` `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
|
usable in **every** position — value, declaration, and type, and optional in the
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Token = @import("token.zig").Token;
|
const Token = @import("token.zig").Token;
|
||||||
const Tag = @import("token.zig").Tag;
|
const Tag = @import("token.zig").Tag;
|
||||||
|
const getKeyword = @import("token.zig").getKeyword;
|
||||||
const Lexer = @import("lexer.zig").Lexer;
|
const Lexer = @import("lexer.zig").Lexer;
|
||||||
const ast = @import("ast.zig");
|
const ast = @import("ast.zig");
|
||||||
const Node = ast.Node;
|
const Node = ast.Node;
|
||||||
@@ -2568,16 +2569,16 @@ pub const Parser = struct {
|
|||||||
// Dereference: expr.*
|
// Dereference: expr.*
|
||||||
self.advance();
|
self.advance();
|
||||||
expr = try self.createNode(expr.span.start, .{ .deref_expr = .{ .operand = expr } });
|
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) {
|
} else if (self.current.tag == .int_literal) {
|
||||||
// Numeric field access: tuple.0, tuple.1
|
// Numeric field access: tuple.0, tuple.1
|
||||||
const field = self.tokenSlice(self.current);
|
const field = self.tokenSlice(self.current);
|
||||||
self.advance();
|
self.advance();
|
||||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
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 {
|
} else {
|
||||||
return self.fail("expected field name or index after '.'");
|
return self.fail("expected field name or index after '.'");
|
||||||
}
|
}
|
||||||
@@ -2960,12 +2961,11 @@ pub const Parser = struct {
|
|||||||
try self.expect(.r_bracket);
|
try self.expect(.r_bracket);
|
||||||
return try self.createNode(start, .{ .array_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
|
return try self.createNode(start, .{ .array_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
|
||||||
}
|
}
|
||||||
// Enum literal: .variant_name
|
// Enum literal: .variant_name. A reserved keyword is a valid
|
||||||
if (self.current.tag != .identifier) {
|
// 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 '.'");
|
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
|
// Enum literal: .variant_name — parsePostfix handles optional (...) as a call
|
||||||
return try self.createNode(start, .{ .enum_literal = .{ .name = name } });
|
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];
|
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} {
|
fn fail(self: *Parser, msg: []const u8) error{ParseError} {
|
||||||
self.err_msg = msg;
|
self.err_msg = msg;
|
||||||
self.err_offset = self.current.loc.start;
|
self.err_offset = self.current.loc.start;
|
||||||
|
|||||||
Reference in New Issue
Block a user