Parse builtin#asm expressions

This commit is contained in:
Lukas Wirth 2024-09-01 13:43:05 +02:00
parent 50882fbfa2
commit 86658c66b4
21 changed files with 865 additions and 31 deletions

View file

@ -15,6 +15,7 @@ extend-ignore-re = [
'"flate2"',
"raison d'être",
"inout",
"INOUT",
"optin"
]

1
Cargo.lock generated
View file

@ -2624,6 +2624,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"directories",
"either",
"flate2",
"itertools",
"proc-macro2",

View file

@ -185,6 +185,7 @@ style = { level = "warn", priority = -1 }
suspicious = { level = "warn", priority = -1 }
## allow following lints
too_long_first_doc_paragraph = "allow"
# subjective
single_match = "allow"
# () makes a fine error in most cases

View file

@ -694,8 +694,11 @@ impl ExprCollector<'_> {
}
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
ast::Expr::AsmExpr(e) => {
let e = self.collect_expr_opt(e.expr());
self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr)
let template = e.template().map(|it| self.collect_expr(it)).collect();
self.alloc_expr(
Expr::InlineAsm(InlineAsm { template, operands: Box::default() }),
syntax_ptr,
)
}
ast::Expr::OffsetOfExpr(e) => {
let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));

View file

@ -307,7 +307,8 @@ pub struct OffsetOf {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineAsm {
pub e: ExprId,
pub template: Box<[ExprId]>,
pub operands: Box<[()]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -372,7 +373,7 @@ impl Expr {
match self {
Expr::Missing => {}
Expr::Path(_) | Expr::OffsetOf(_) => {}
Expr::InlineAsm(it) => f(it.e),
Expr::InlineAsm(it) => it.template.iter().copied().for_each(f),
Expr::If { condition, then_branch, else_branch } => {
f(*condition);
f(*then_branch);

View file

@ -666,7 +666,9 @@ impl InferenceContext<'_> {
fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
match &self.body[tgt_expr] {
Expr::OffsetOf(_) => (),
Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e),
Expr::InlineAsm(e) => {
e.template.iter().for_each(|it| self.walk_expr_without_adjust(*it))
}
Expr::If { condition, then_branch, else_branch } => {
self.consume_expr(*condition);
self.consume_expr(*then_branch);

View file

@ -925,7 +925,7 @@ impl InferenceContext<'_> {
}
Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
Expr::InlineAsm(it) => {
self.infer_expr_no_expect(it.e);
it.template.iter().for_each(|&expr| _ = self.infer_expr_no_expect(expr));
self.result.standard_types.unit.clone()
}
};

View file

@ -39,7 +39,10 @@ impl InferenceContext<'_> {
fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) {
match &self.body[tgt_expr] {
Expr::Missing => (),
Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not),
Expr::InlineAsm(e) => e
.template
.iter()
.for_each(|&expr| self.infer_mut_expr_without_adjust(expr, Mutability::Not)),
Expr::OffsetOf(_) => (),
&Expr::If { condition, then_branch, else_branch } => {
self.infer_mut_expr(condition, Mutability::Not);
@ -129,7 +132,7 @@ impl InferenceContext<'_> {
target,
}) = base_adjustments
{
// For assignee exprs `IndexMut` obiligations are already applied
// For assignee exprs `IndexMut` obligations are already applied
if !is_assignee_expr {
if let TyKind::Ref(_, _, ty) = target.kind(Interner) {
base_ty = Some(ty.clone());

View file

@ -245,7 +245,7 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
// test builtin_expr
// fn foo() {
// builtin#asm(0);
// builtin#asm("");
// builtin#format_args("", 0, 1, a = 2 + 3, a + b);
// builtin#offset_of(Foo, bar.baz.0);
// }
@ -297,18 +297,175 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
p.expect(T![')']);
Some(m.complete(p, FORMAT_ARGS_EXPR))
} else if p.at_contextual_kw(T![asm]) {
p.bump_remap(T![asm]);
p.expect(T!['(']);
// FIXME: We just put expression here so highlighting kind of keeps working
expr(p);
p.expect(T![')']);
Some(m.complete(p, ASM_EXPR))
parse_asm_expr(p, m)
} else {
m.abandon(p);
None
}
}
// test asm_expr
// fn foo() {
// builtin#asm(
// "mov {tmp}, {x}",
// "shl {tmp}, 1",
// "shl {x}, 2",
// "add {x}, {tmp}",
// x = inout(reg) x,
// tmp = out(reg) _,
// );
// }
fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
p.bump_remap(T![asm]);
p.expect(T!['(']);
if expr(p).is_none() {
p.err_and_bump("expected asm template");
}
let mut allow_templates = true;
while !p.at(EOF) && !p.at(T![')']) {
p.expect(T![,]);
// accept trailing commas
if p.at(T![')']) {
break;
}
// Parse clobber_abi
if p.eat_contextual_kw(T![clobber_abi]) {
parse_clobber_abi(p);
allow_templates = false;
continue;
}
// Parse options
if p.eat_contextual_kw(T![options]) {
parse_options(p);
allow_templates = false;
continue;
}
// Parse operand names
if p.at(T![ident]) && p.nth_at(1, T![=]) {
name(p);
p.bump(T![=]);
allow_templates = false;
true
} else {
false
};
let op = p.start();
if p.eat(T![in]) {
parse_reg(p);
expr(p);
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![out]) {
parse_reg(p);
expr(p);
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![lateout]) {
parse_reg(p);
expr(p);
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![inout]) {
parse_reg(p);
expr(p);
if p.eat(T![=>]) {
expr(p);
}
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![inlateout]) {
parse_reg(p);
expr(p);
if p.eat(T![=>]) {
expr(p);
}
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![label]) {
block_expr(p);
op.complete(p, ASM_LABEL);
} else if p.eat(T![const]) {
expr(p);
op.complete(p, ASM_CONST);
} else if p.eat_contextual_kw(T![sym]) {
expr(p);
op.complete(p, ASM_SYM);
} else if allow_templates {
op.abandon(p);
if expr(p).is_none() {
p.err_and_bump("expected asm template");
}
continue;
} else {
op.abandon(p);
p.err_and_bump("expected asm operand");
if p.at(T!['}']) {
break;
}
continue;
};
allow_templates = false;
}
p.expect(T![')']);
Some(m.complete(p, ASM_EXPR))
}
fn parse_options(p: &mut Parser<'_>) {
p.expect(T!['(']);
while !p.eat(T![')']) && !p.at(EOF) {
const OPTIONS: &[SyntaxKind] = &[
T![pure],
T![nomem],
T![readonly],
T![preserves_flags],
T![noreturn],
T![nostack],
T![may_unwind],
T![att_syntax],
T![raw],
];
if !OPTIONS.iter().any(|&syntax| p.eat(syntax)) {
p.err_and_bump("expected asm option");
continue;
}
// Allow trailing commas
if p.eat(T![')']) {
break;
}
p.expect(T![,]);
}
}
fn parse_clobber_abi(p: &mut Parser<'_>) {
p.expect(T!['(']);
while !p.eat(T![')']) && !p.at(EOF) {
if !p.expect(T![string]) {
break;
}
// Allow trailing commas
if p.eat(T![')']) {
break;
}
p.expect(T![,]);
}
}
fn parse_reg(p: &mut Parser<'_>) {
p.expect(T!['(']);
if p.at(T![ident]) {
name_ref(p)
} else if p.at(T![string]) {
p.bump_any()
} else {
p.err_and_bump("expected register name");
}
p.expect(T![')']);
}
// test array_expr
// fn foo() {
// [];

View file

@ -131,6 +131,14 @@ impl<'t> Parser<'t> {
true
}
pub(crate) fn eat_contextual_kw(&mut self, kind: SyntaxKind) -> bool {
if !self.at_contextual_kw(kind) {
return false;
}
self.bump_remap(kind);
true
}
fn at_composite2(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind) -> bool {
self.inp.kind(self.pos + n) == k1
&& self.inp.kind(self.pos + n + 1) == k2

File diff suppressed because one or more lines are too long

View file

@ -19,6 +19,8 @@ mod ok {
#[test]
fn as_precedence() { run_and_expect_no_errors("test_data/parser/inline/ok/as_precedence.rs"); }
#[test]
fn asm_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_expr.rs"); }
#[test]
fn assoc_const_eq() {
run_and_expect_no_errors("test_data/parser/inline/ok/assoc_const_eq.rs");
}

View file

@ -0,0 +1,77 @@
SOURCE_FILE
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "foo"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
ASM_EXPR
BUILTIN_KW "builtin"
POUND "#"
ASM_KW "asm"
L_PAREN "("
WHITESPACE "\n "
LITERAL
STRING "\"mov {tmp}, {x}\""
COMMA ","
WHITESPACE "\n "
LITERAL
STRING "\"shl {tmp}, 1\""
COMMA ","
WHITESPACE "\n "
LITERAL
STRING "\"shl {x}, 2\""
COMMA ","
WHITESPACE "\n "
LITERAL
STRING "\"add {x}, {tmp}\""
COMMA ","
WHITESPACE "\n "
NAME
IDENT "x"
WHITESPACE " "
EQ "="
WHITESPACE " "
ASM_REG_OPERAND
INOUT_KW "inout"
L_PAREN "("
NAME_REF
IDENT "reg"
R_PAREN ")"
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "x"
COMMA ","
WHITESPACE "\n "
NAME
IDENT "tmp"
WHITESPACE " "
EQ "="
WHITESPACE " "
ASM_REG_OPERAND
OUT_KW "out"
L_PAREN "("
NAME_REF
IDENT "reg"
R_PAREN ")"
WHITESPACE " "
UNDERSCORE_EXPR
UNDERSCORE "_"
COMMA ","
WHITESPACE "\n "
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"

View file

@ -0,0 +1,10 @@
fn foo() {
builtin#asm(
"mov {tmp}, {x}",
"shl {tmp}, 1",
"shl {x}, 2",
"add {x}, {tmp}",
x = inout(reg) x,
tmp = out(reg) _,
);
}

View file

@ -19,7 +19,7 @@ SOURCE_FILE
ASM_KW "asm"
L_PAREN "("
LITERAL
INT_NUMBER "0"
STRING "\"\""
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "

View file

@ -1,5 +1,5 @@
fn foo() {
builtin#asm(0);
builtin#asm("");
builtin#format_args("", 0, 1, a = 2 + 3, a + b);
builtin#offset_of(Foo, bar.baz.0);
}

View file

@ -391,8 +391,33 @@ Expr =
OffsetOfExpr =
Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
// asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")"
// global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
// format_string := STRING_LITERAL / RAW_STRING_LITERAL
AsmExpr =
Attr* 'builtin' '#' 'asm' '(' Expr ')'
Attr* 'builtin' '#' 'asm' '(' template:(Expr (',' Expr)*) (AsmOperand (',' AsmOperand)*)? ','? ')'
// operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_"
AsmOperandExpr = in_expr:Expr ('=>' out_expr:Expr)?
// dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout"
AsmDirSpec = 'in' | 'out' | 'lateout' | 'inout' | 'inlateout'
// reg_spec := <register class> / "\"" <explicit register> "\""
AsmRegSpec = '@string' | NameRef
// reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr
AsmRegOperand = (Name '=')? AsmDirSpec '(' AsmRegSpec ')' AsmOperandExpr
// clobber_abi := "clobber_abi(" <abi> *("," <abi>) [","] ")"
AsmClobberAbi = 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')'
// option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw"
AsmOption = 'pure' | 'nomem' | 'readonly' | 'preserves_flags' | 'noreturn' | 'nostack' | 'att_syntax' | 'raw' | 'may_unwind'
// options := "options(" option *("," option) [","] ")"
AsmOptions = 'options' '(' AsmOption *(',' AsmOption) ','? ')'
// operand := reg_operand / clobber_abi / options
AsmOperand = AsmRegOperand | AsmClobberAbi | AsmOptions | AsmLabel
AsmLabel = 'label' BlockExpr
AsmSym = 'sym' Expr
AsmConst = 'const' Expr
FormatArgsExpr =
Attr* 'builtin' '#' 'format_args' '('

View file

@ -64,6 +64,53 @@ impl ArrayType {
pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmClobberAbi {
pub(crate) syntax: SyntaxNode,
}
impl AsmClobberAbi {
#[inline]
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
#[inline]
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
#[inline]
pub fn clobber_abi_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![clobber_abi])
}
#[inline]
pub fn string_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![string]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmConst {
pub(crate) syntax: SyntaxNode,
}
impl AsmConst {
#[inline]
pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
#[inline]
pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmDirSpec {
pub(crate) syntax: SyntaxNode,
}
impl AsmDirSpec {
#[inline]
pub fn in_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![in]) }
#[inline]
pub fn inlateout_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![inlateout])
}
#[inline]
pub fn inout_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![inout]) }
#[inline]
pub fn lateout_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![lateout]) }
#[inline]
pub fn out_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![out]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmExpr {
pub(crate) syntax: SyntaxNode,
@ -71,7 +118,9 @@ pub struct AsmExpr {
impl ast::HasAttrs for AsmExpr {}
impl AsmExpr {
#[inline]
pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
pub fn asm_operands(&self) -> AstChildren<AsmOperand> { support::children(&self.syntax) }
#[inline]
pub fn template(&self) -> AstChildren<Expr> { support::children(&self.syntax) }
#[inline]
pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) }
#[inline]
@ -79,11 +128,133 @@ impl AsmExpr {
#[inline]
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
#[inline]
pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
#[inline]
pub fn asm_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![asm]) }
#[inline]
pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmLabel {
pub(crate) syntax: SyntaxNode,
}
impl AsmLabel {
#[inline]
pub fn block_expr(&self) -> Option<BlockExpr> { support::child(&self.syntax) }
#[inline]
pub fn label_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![label]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmOperandExpr {
pub(crate) syntax: SyntaxNode,
}
impl AsmOperandExpr {
#[inline]
pub fn in_expr(&self) -> Option<Expr> { support::child(&self.syntax) }
#[inline]
pub fn out_expr(&self) -> Option<Expr> { support::child(&self.syntax) }
#[inline]
pub fn fat_arrow_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=>]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmOption {
pub(crate) syntax: SyntaxNode,
}
impl AsmOption {
#[inline]
pub fn att_syntax_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![att_syntax])
}
#[inline]
pub fn may_unwind_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![may_unwind])
}
#[inline]
pub fn nomem_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![nomem]) }
#[inline]
pub fn noreturn_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![noreturn])
}
#[inline]
pub fn nostack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![nostack]) }
#[inline]
pub fn preserves_flags_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![preserves_flags])
}
#[inline]
pub fn pure_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![pure]) }
#[inline]
pub fn raw_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![raw]) }
#[inline]
pub fn readonly_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![readonly])
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmOptions {
pub(crate) syntax: SyntaxNode,
}
impl AsmOptions {
#[inline]
pub fn asm_option(&self) -> Option<AsmOption> { support::child(&self.syntax) }
#[inline]
pub fn asm_options(&self) -> AstChildren<AsmOption> { support::children(&self.syntax) }
#[inline]
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
#[inline]
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
#[inline]
pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
#[inline]
pub fn options_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![options]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmRegOperand {
pub(crate) syntax: SyntaxNode,
}
impl ast::HasName for AsmRegOperand {}
impl AsmRegOperand {
#[inline]
pub fn asm_dir_spec(&self) -> Option<AsmDirSpec> { support::child(&self.syntax) }
#[inline]
pub fn asm_operand_expr(&self) -> Option<AsmOperandExpr> { support::child(&self.syntax) }
#[inline]
pub fn asm_reg_spec(&self) -> Option<AsmRegSpec> { support::child(&self.syntax) }
#[inline]
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
#[inline]
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
#[inline]
pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmRegSpec {
pub(crate) syntax: SyntaxNode,
}
impl AsmRegSpec {
#[inline]
pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) }
#[inline]
pub fn string_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![string]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsmSym {
pub(crate) syntax: SyntaxNode,
}
impl AsmSym {
#[inline]
pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
#[inline]
pub fn sym_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![sym]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AssocItemList {
pub(crate) syntax: SyntaxNode,
@ -2051,6 +2222,14 @@ impl ast::HasGenericParams for Adt {}
impl ast::HasName for Adt {}
impl ast::HasVisibility for Adt {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AsmOperand {
AsmClobberAbi(AsmClobberAbi),
AsmLabel(AsmLabel),
AsmOptions(AsmOptions),
AsmRegOperand(AsmRegOperand),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AssocItem {
Const(Const),
@ -2316,6 +2495,48 @@ impl AstNode for ArrayType {
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmClobberAbi {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_CLOBBER_ABI }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmConst {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_CONST }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmDirSpec {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_DIR_SPEC }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmExpr {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_EXPR }
@ -2330,6 +2551,104 @@ impl AstNode for AsmExpr {
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmLabel {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_LABEL }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmOperandExpr {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPERAND_EXPR }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmOption {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPTION }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmOptions {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPTIONS }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmRegOperand {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_REG_OPERAND }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmRegSpec {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_REG_SPEC }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AsmSym {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_SYM }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for AssocItemList {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == ASSOC_ITEM_LIST }
@ -4268,6 +4587,48 @@ impl AstNode for Adt {
}
}
}
impl From<AsmClobberAbi> for AsmOperand {
#[inline]
fn from(node: AsmClobberAbi) -> AsmOperand { AsmOperand::AsmClobberAbi(node) }
}
impl From<AsmLabel> for AsmOperand {
#[inline]
fn from(node: AsmLabel) -> AsmOperand { AsmOperand::AsmLabel(node) }
}
impl From<AsmOptions> for AsmOperand {
#[inline]
fn from(node: AsmOptions) -> AsmOperand { AsmOperand::AsmOptions(node) }
}
impl From<AsmRegOperand> for AsmOperand {
#[inline]
fn from(node: AsmRegOperand) -> AsmOperand { AsmOperand::AsmRegOperand(node) }
}
impl AstNode for AsmOperand {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool {
matches!(kind, ASM_CLOBBER_ABI | ASM_LABEL | ASM_OPTIONS | ASM_REG_OPERAND)
}
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() {
ASM_CLOBBER_ABI => AsmOperand::AsmClobberAbi(AsmClobberAbi { syntax }),
ASM_LABEL => AsmOperand::AsmLabel(AsmLabel { syntax }),
ASM_OPTIONS => AsmOperand::AsmOptions(AsmOptions { syntax }),
ASM_REG_OPERAND => AsmOperand::AsmRegOperand(AsmRegOperand { syntax }),
_ => return None,
};
Some(res)
}
#[inline]
fn syntax(&self) -> &SyntaxNode {
match self {
AsmOperand::AsmClobberAbi(it) => &it.syntax,
AsmOperand::AsmLabel(it) => &it.syntax,
AsmOperand::AsmOptions(it) => &it.syntax,
AsmOperand::AsmRegOperand(it) => &it.syntax,
}
}
}
impl From<Const> for AssocItem {
#[inline]
fn from(node: Const) -> AssocItem { AssocItem::Const(node) }
@ -5803,7 +6164,8 @@ impl AstNode for AnyHasName {
fn can_cast(kind: SyntaxKind) -> bool {
matches!(
kind,
CONST
ASM_REG_OPERAND
| CONST
| CONST_PARAM
| ENUM
| FN
@ -5832,6 +6194,10 @@ impl AstNode for AnyHasName {
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl From<AsmRegOperand> for AnyHasName {
#[inline]
fn from(node: AsmRegOperand) -> AnyHasName { AnyHasName { syntax: node.syntax } }
}
impl From<Const> for AnyHasName {
#[inline]
fn from(node: Const) -> AnyHasName { AnyHasName { syntax: node.syntax } }
@ -6072,6 +6438,11 @@ impl std::fmt::Display for Adt {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmOperand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AssocItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
@ -6142,11 +6513,61 @@ impl std::fmt::Display for ArrayType {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmClobberAbi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmConst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmDirSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmLabel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmOperandExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmRegOperand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmRegSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AsmSym {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for AssocItemList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)

View file

@ -19,6 +19,7 @@ stdx.workspace = true
proc-macro2 = "1.0.47"
quote = "1.0.20"
ungrammar = "1.16.1"
either.workspace = true
itertools.workspace = true
# Avoid adding more dependencies to this crate

View file

@ -11,9 +11,11 @@ use std::{
fs,
};
use either::Either;
use itertools::Itertools;
use proc_macro2::{Punct, Spacing};
use quote::{format_ident, quote};
use stdx::panic_context;
use ungrammar::{Grammar, Rule};
use crate::{
@ -462,6 +464,7 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
let tokens = grammar.tokens.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>();
// FIXME: This generates enum kinds?
let nodes = grammar.nodes.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>();
let ast = quote! {
@ -711,6 +714,7 @@ fn lower(grammar: &Grammar) -> AstSrc {
for &node in &nodes {
let name = grammar[node].name.clone();
let rule = &grammar[node].rule;
let _g = panic_context::enter(name.clone());
match lower_enum(grammar, rule) {
Some(variants) => {
let enum_src = AstEnumSrc { doc: Vec::new(), name, traits: Vec::new(), variants };
@ -838,11 +842,16 @@ fn lower_separated_list(
Rule::Seq(it) => it,
_ => return false,
};
let (node, repeat, trailing_sep) = match rule.as_slice() {
let (nt, repeat, trailing_sep) = match rule.as_slice() {
[Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
(node, repeat, Some(trailing_sep))
(Either::Left(node), repeat, Some(trailing_sep))
}
[Rule::Node(node), Rule::Rep(repeat)] => (node, repeat, None),
[Rule::Node(node), Rule::Rep(repeat)] => (Either::Left(node), repeat, None),
[Rule::Token(token), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
(Either::Right(token), repeat, Some(trailing_sep))
}
[Rule::Token(token), Rule::Rep(repeat)] => (Either::Right(token), repeat, None),
_ => return false,
};
let repeat = match &**repeat {
@ -851,15 +860,28 @@ fn lower_separated_list(
};
if !matches!(
repeat.as_slice(),
[comma, Rule::Node(n)]
if trailing_sep.map_or(true, |it| comma == &**it) && n == node
[comma, nt_]
if trailing_sep.map_or(true, |it| comma == &**it) && match (nt, nt_) {
(Either::Left(node), Rule::Node(nt_)) => node == nt_,
(Either::Right(token), Rule::Token(nt_)) => token == nt_,
_ => false,
}
) {
return false;
}
let ty = grammar[*node].name.clone();
let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
let field = Field::Node { name, ty, cardinality: Cardinality::Many };
acc.push(field);
match nt {
Either::Right(token) => {
let name = clean_token_name(&grammar[*token].name);
let field = Field::Token(name);
acc.push(field);
}
Either::Left(node) => {
let ty = grammar[*node].name.clone();
let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
let field = Field::Node { name, ty, cardinality: Cardinality::Many };
acc.push(field);
}
}
true
}

View file

@ -113,7 +113,31 @@ const RESERVED: &[&str] = &[
const CONTEXTUAL_KEYWORDS: &[&str] =
&["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet"];
// keywords we use for special macro expansions
const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &["builtin", "offset_of", "format_args", "asm"];
const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[
"asm",
"att_syntax",
"builtin",
"clobber_abi",
"format_args",
// "in",
"inlateout",
"inout",
"label",
"lateout",
"may_unwind",
"nomem",
"noreturn",
"nostack",
"offset_of",
"options",
"out",
"preserves_flags",
"pure",
// "raw",
"readonly",
"sym",
];
// keywords that are keywords depending on the edition
const EDITION_DEPENDENT_KEYWORDS: &[(&str, Edition)] = &[
("try", Edition::Edition2018),