From 9f36b7ef7f744132ec5d8f9d8ad6ae4c30906179 Mon Sep 17 00:00:00 2001 From: nothendev Date: Fri, 5 May 2023 14:21:24 +0300 Subject: [PATCH] enumerations --- programs/aidl/assets/core.idl | 18 +-- programs/aidl/assets/why.idl | 22 +++- programs/aidl/src/ast.rs | 88 +++++++++++++-- programs/aidl/src/lexer.rs | 15 ++- programs/aidl/src/main.rs | 14 ++- programs/aidl/src/parser/enumeration.rs | 133 ++++++++++++++++++++++ programs/aidl/src/parser/expr.rs | 38 ++++--- programs/aidl/src/parser/interface.rs | 140 ++++++++++++++++++++++++ programs/aidl/src/parser/mod.rs | 61 ++++++----- programs/aidl/src/parser/structure.rs | 44 ++++++++ programs/aidl/src/parser/types.rs | 45 ++++++++ 11 files changed, 547 insertions(+), 71 deletions(-) create mode 100644 programs/aidl/src/parser/enumeration.rs create mode 100644 programs/aidl/src/parser/types.rs diff --git a/programs/aidl/assets/core.idl b/programs/aidl/assets/core.idl index 508ba9c..1067197 100644 --- a/programs/aidl/assets/core.idl +++ b/programs/aidl/assets/core.idl @@ -1,19 +1,21 @@ -Type Byte = U8; -Type Int = U32; -Type String = Vector; +Alias Byte = U8; +Alias Int = U32; +Alias String = Vector; -Enumurate Boolean { +Enumeration Boolean { False = 0, True = 1, } -Union Option{ - None, - Some +Enumeration Nothing {} + +Enumeration Option { + None, + Some(T) } Structure Version { major: Byte, minor: Byte, patch: Byte, -}; +} diff --git a/programs/aidl/assets/why.idl b/programs/aidl/assets/why.idl index e1ca115..e39c329 100644 --- a/programs/aidl/assets/why.idl +++ b/programs/aidl/assets/why.idl @@ -5,7 +5,25 @@ Constant Hi = "WHY???/\n"; Alias Yo = Byte; Constant Version = Make Version { - major: 1, - minor: 0, + major: 1 + minor: 0 patch: 0 }; + +Interface Iface { + Function hello Takes(Int Boolean) Returns(Int); +} + +Function a_free_function Returns(Boolean); + +Structure Hello { + world: Boolean prompt: Option +} + +Enumeration Reality { + Dead(Boolean Boolean), + Alive { + health: Int + dying: Boolean + } +} diff --git a/programs/aidl/src/ast.rs b/programs/aidl/src/ast.rs index 9c18491..78b7ee3 100644 --- a/programs/aidl/src/ast.rs +++ b/programs/aidl/src/ast.rs @@ -17,20 +17,60 @@ pub struct IDLModule { #[derive(Debug)] pub enum Item { - _Interface(ItemInterface), + Interface(ItemInterface), Alias(ItemAlias), Constant(ItemConstant), + Function(Function), + Structure(ItemStructure), + Enumeration(ItemEnumeration) } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Function { pub name: String, pub takes: Vec, - pub returns: Type + pub returns: Type, } -// why -pub type Type = String; +#[derive(Debug)] +pub struct Type { + pub name: String, + pub arguments: TypeArguments, +} + +impl Type { + pub fn infer() -> Self { + Self { + name: String::from(INFER_TYPE), + arguments: TypeArguments::None + } + } +} + +pub const NOTHING_TYPE: &str = "Nothing"; +pub const INFER_TYPE: &str = "_"; + +impl Default for Type { + fn default() -> Self { + Self { + name: String::from(NOTHING_TYPE), + arguments: TypeArguments::None, + } + } +} + +#[derive(Debug, Default)] +pub enum TypeArguments { + /// TypeName + #[default] + None, + /// TypeName + AngleBracketed(Vec>), +} + +pub fn nothing() -> Type { + Type::default() +} #[derive(Debug)] pub struct ItemInterface { @@ -38,16 +78,44 @@ pub struct ItemInterface { pub functions: Vec, } +#[derive(Debug)] +pub struct ItemStructure { + pub name: String, + pub fields: HashMap, +} + #[derive(Debug)] pub struct ItemAlias { pub name: String, - pub referree: String, + pub referree: Type, } #[derive(Debug)] pub struct ItemConstant { pub name: String, - pub expr: Expr + pub expr: Expr, +} + +#[derive(Debug)] +pub struct ItemEnumeration { + pub name: String, + pub arguments: TypeArguments, + pub variants: Vec +} + +#[derive(Debug)] +pub struct EnumerationVariant { + pub name: String, + pub content: EnumerationContent +} + +#[derive(Debug, Default)] +pub enum EnumerationContent { + #[default] + None, + Tuple(Vec), + Structure(HashMap), + Value(NumberLiteral) } #[derive(Debug)] @@ -59,20 +127,20 @@ pub struct UseDecl { pub enum Expr { Literal(Literal), _IdentAccess(String), - Make(Box) + Make(Box), } #[derive(Debug)] pub struct ExprMake { pub name: String, - pub params: HashMap + pub params: HashMap, } #[derive(Debug)] pub enum Literal { String(String), Number(NumberLiteral), - Char(char) + Char(char), } #[derive(Debug)] diff --git a/programs/aidl/src/lexer.rs b/programs/aidl/src/lexer.rs index d169678..0e11bd6 100644 --- a/programs/aidl/src/lexer.rs +++ b/programs/aidl/src/lexer.rs @@ -1,6 +1,6 @@ use std::{ fmt::Display, - ops::{Add, Range, AddAssign}, + ops::{Add, AddAssign, Range}, }; use logos::Logos; @@ -51,7 +51,10 @@ pub enum Token { #[regex(r#"(-)?\d+"#, |lex| lex.slice().parse().ok())] NumberLiteral(i64), - #[regex(r"(ptr|u8|i8|u16|i16|u32|i32|u64|i64|f32|f64)", |lex| NumberSuffix::lexer(lex.slice()).next().and_then(Result::ok))] + #[regex( + r"(ptr|u8|i8|u16|i16|u32|i32|u64|i64|f32|f64)", + |lex| NumberSuffix::lexer(lex.slice()).next().and_then(Result::ok) + )] NumberSuffix(NumberSuffix), #[regex(r#"[a-zA-Z_][a-zA-Z\d_]*"#, |lex| Ident::lexer(lex.slice()).next().and_then(Result::ok))] @@ -73,10 +76,18 @@ pub enum Ident { Structure, #[token("Alias")] Alias, + #[token("Enumeration")] + Enumeration, #[token("Use")] Use, #[token("Make")] Make, + #[token("Takes")] + Takes, + #[token("Returns")] + Returns, + #[token("_")] + Underscore, #[regex(r"[a-zA-Z_][a-zA-Z\d_]*", |lex| lex.slice().parse().ok())] Other(String), } diff --git a/programs/aidl/src/main.rs b/programs/aidl/src/main.rs index 179bd3e..62527ba 100644 --- a/programs/aidl/src/main.rs +++ b/programs/aidl/src/main.rs @@ -36,9 +36,15 @@ fn main() { parser::ParserError::UnexpectedEOF => Label::primary( (), (codespan_file.source().len() - 1)..codespan_file.source().len(), - ).with_message("Unexpected end of file here"), - parser::ParserError::Unexpected(expected, Spanned(got, span)) => Label::primary((), span.0).with_message(format!("Unexpected `{got}`, expected {expected}")), - parser::ParserError::PleaseStopParsingUse => unreachable!() + ) + .with_message("Unexpected end of file here"), + parser::ParserError::Unexpected(expected, Spanned(got, span)) => { + Label::primary((), span.0) + .with_message(format!("Unexpected `{got}`, expected {expected}")) + } + parser::ParserError::PleaseStopParsingUse => unsafe { + std::hint::unreachable_unchecked() + }, }; let diagnostic = codespan_reporting::diagnostic::Diagnostic::error() .with_message(msg) @@ -52,6 +58,8 @@ fn main() { .unwrap(); } } + } else { + eprintln!("No file given. Aborting."); } } diff --git a/programs/aidl/src/parser/enumeration.rs b/programs/aidl/src/parser/enumeration.rs new file mode 100644 index 0000000..c6d728c --- /dev/null +++ b/programs/aidl/src/parser/enumeration.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; + +use crate::{ + ast::{EnumerationContent, EnumerationVariant, ItemEnumeration, Type}, + lexer::{Ident, Spanned, Token}, +}; + +use super::{Parser, ParserError}; + +impl<'a> Parser<'a> { + pub fn ask_enumeration(&mut self) -> Result, ParserError> { + let Spanned(_, span) = self.get_real( + |token| matches!(token, Token::Ident(Ident::Enumeration)), + "the `Enumeration` keyword", + )?; + + let Spanned(Type { name, arguments }, _) = self.ask_type()?; + + self.get_real( + |token| matches!(token, Token::LeftCurly), + "an opening curly brace", + )?; + + let mut variants = vec![]; + + loop { + match self.tokens.peek()?.0 { + Token::Ident(Ident::Other(_)) => { + let Spanned(variant_name, _) = self.ask_ident()?; + + let mut content = EnumerationContent::None; + + loop { + match self.tokens.peek()?.0 { + Token::LeftParen => { + self.eat(); + + let mut tuple = vec![]; + + loop { + match self.tokens.peek()?.0 { + Token::Ident(Ident::Other(_)) => { + tuple.push(self.ask_type()?.0); + if let Token::Comma = self.tokens.peek()?.0 { + self.eat(); + }; + } + Token::RightParen => { + self.eat(); + break; + } + _ => { + return Err( + self.expected("closing parentheses or a type") + ) + } + } + } + content = EnumerationContent::Tuple(tuple); + } + Token::LeftCurly => { + self.eat(); + + let mut structure = HashMap::::new(); + + loop { + match self.tokens.peek()?.0 { + Token::Ident(Ident::Other(_)) => { + let Spanned(field_name, _) = self.ask_ident()?; + + self.get_real( + |token| matches!(token, Token::Colon), + "a colon", + )?; + + structure.insert(field_name, self.ask_type()?.0); + if let Token::Comma = self.tokens.peek()?.0 { + self.eat(); + }; + } + Token::RightCurly => { + self.eat(); + break; + } + _ => { + return Err( + self.expected("closing parentheses or a type") + ) + } + } + } + + content = EnumerationContent::Structure(structure); + } + Token::Equals => { + self.eat(); + + content = EnumerationContent::Value(self._ask_number_literal()?.0); + } + _ => break, + } + } + + if let Spanned(Token::Comma, _) = self.tokens.peek()? { + self.eat(); + } + + variants.push(EnumerationVariant { + name: variant_name, + content, + }); + } + Token::RightCurly => break, + _ => return Err(self.expected("a closing curly brace or a variant")), + } + } + + if let Spanned(Token::RightCurly, _) = self.tokens.peek()? { + self.eat(); + + return Ok(Spanned( + ItemEnumeration { + name, + arguments, + variants, + }, + span + self.tokens.span(), + )); + }; + + Err(self.expected("???")) + } +} diff --git a/programs/aidl/src/parser/expr.rs b/programs/aidl/src/parser/expr.rs index c793206..ff2632c 100644 --- a/programs/aidl/src/parser/expr.rs +++ b/programs/aidl/src/parser/expr.rs @@ -17,26 +17,20 @@ impl<'a> Parser<'a> { } Token::Ident(Ident::Make) => { self.eat(); - self._ask_struct_init()? - .map(Box::new) - .map(Expr::Make) + self._ask_struct_init()?.map(Box::new).map(Expr::Make) } _ => return Err(self.expected("an expression")), }) } - fn _ask_literal(&mut self) -> Result, ParserError> { - let Spanned(token, mut span) = self.tokens.next()?; - Ok(match token { - Token::StringLiteral(string) => Spanned(Literal::String(string), span), - Token::CharLiteral(chr) => Spanned(Literal::Char(chr), span), - Token::NumberLiteral(number) => { + pub fn _ask_number_literal(&mut self) -> Result, ParserError> { + match self.tokens.next()? { + Spanned(Token::NumberLiteral(number), mut span) => { let lit = if let Spanned(Token::NumberSuffix(_), sp) = self.tokens.peek()? { span += sp; use NumberLiteral::*; - Literal::Number( - match unwrap_match!( + match unwrap_match!( self.tokens.next()?, Spanned(Token::NumberSuffix(suffering), _) => suffering) // eat suffix { NumberSuffix::Ptr => Ptr(number as usize), @@ -49,14 +43,26 @@ impl<'a> Parser<'a> { NumberSuffix::U64 => U64(number as u64), NumberSuffix::I64 => I64(number), _ => return Err(self.expected("a non-floating number suffix")) - }, - ) + } } else { - Literal::Number(NumberLiteral::Infer(number)) + NumberLiteral::Infer(number) }; - Spanned(lit, span) + Ok(Spanned(lit, span)) } + _ => Err(self.expected("a number literal")), + } + } + + pub fn _ask_literal(&mut self) -> Result, ParserError> { + if let Spanned(Token::NumberLiteral(_), _) = self.tokens.peek()? { + return Ok(self._ask_number_literal()?.map(Literal::Number)); + }; + let Spanned(token, span) = self.tokens.next()?; + Ok(match token { + Token::StringLiteral(string) => Spanned(Literal::String(string), span), + Token::CharLiteral(chr) => Spanned(Literal::Char(chr), span), + _ => return Err(self.expected("a literal")), }) } @@ -90,6 +96,6 @@ impl<'a> Parser<'a> { return Ok(Spanned(ExprMake { name, params }, nSp + ccSp)); }; - Err(self.expected("something")) + Err(self.expected("closing curly braces")) } } diff --git a/programs/aidl/src/parser/interface.rs b/programs/aidl/src/parser/interface.rs index e69de29..651ad18 100644 --- a/programs/aidl/src/parser/interface.rs +++ b/programs/aidl/src/parser/interface.rs @@ -0,0 +1,140 @@ +use crate::{ + ast::{nothing, Function, ItemInterface}, + lexer::{Ident, Span, Spanned, Token}, +}; + +use super::{Parser, ParserError}; + +impl<'a> Parser<'a> { + pub fn ask_interface(&mut self) -> Result, ParserError> { + // Interface + let Spanned(_, mut span) = self.get_real( + |token| matches!(token, Token::Ident(Ident::Interface)), + "the `Interface` keyword", + )?; + // InterfaceName + let Spanned(name, _) = self.ask_ident()?; + + // { + self.get_real( + |token| matches!(token, Token::LeftCurly), + "opening curly brackets", + )?; + + let mut functions = vec![]; + + loop { + match self.tokens.peek()? { + Spanned(Token::RightCurly, end) => { + self.eat(); + span += end; + break; + } + Spanned(Token::Ident(Ident::Function), _) => functions.push(self.ask_function()?.0), + _ => return Err(self.expected("A function or closing curly braces")), + } + } + + Ok(Spanned(ItemInterface { name, functions }, span)) + } + + pub fn ask_function(&mut self) -> Result, ParserError> { + let Spanned(_, bsp) = self.get_real( + |token| matches!(token, Token::Ident(Ident::Function)), + "the `Function` keyword", + )?; + + let Spanned(name, _) = self.ask_ident()?; + + let Spanned(next, esp) = self.tokens.next()?; + match next { + Token::Ident(Ident::Takes) => { + self.get_real( + |token| matches!(token, Token::LeftParen), + "Opening parentheses", + )?; + + let mut takes = vec![]; + let mut returns = nothing(); + loop { + let Spanned(peeked, _) = self.tokens.peek()?; + match peeked { + Token::Ident(_) => { + takes.push(self.ask_type()?.0); + if let Token::Comma = self.tokens.peek()?.0 { + self.eat(); + }; + } + Token::RightParen => { + self.eat(); + break; + } + _ => return Err(self.expected("closing parentheses or a type name")), + } + } + + match self.tokens.next()?.0 { + Token::Semicolon => {} + Token::Ident(Ident::Returns) => { + self.get_real( + |token| matches!(token, Token::LeftParen), + "Opening parentheses", + )?; + + let Spanned(returns_, _) = self.ask_type()?; + + returns = returns_; + + self.get_real( + |token| matches!(token, Token::RightParen), + "Closing parentheses", + )?; + + self.semi()?; + } + _ => return Err(self.expected("a semicolon or a Returns clause")), + } + + Ok(Spanned( + Function { + name, + takes, + returns, + }, + bsp + Span(self.tokens.lexer.span()), + )) + } + Token::Ident(Ident::Returns) => { + self.get_real( + |token| matches!(token, Token::LeftParen), + "Opening parentheses", + )?; + + let Spanned(returns, _) = self.ask_type()?; + + self.get_real( + |token| matches!(token, Token::RightParen), + "Closing parentheses", + )?; + + Ok(Spanned( + Function { + name, + takes: Vec::new(), + returns, + }, + bsp + self.semi()?, + )) + } + Token::Semicolon => Ok(Spanned( + Function { + name, + takes: Vec::new(), + returns: nothing(), + }, + bsp + esp, + )), + _ => Err(self.expected("a Takes clause, a Returns clause or a semicolon")), + } + } +} diff --git a/programs/aidl/src/parser/mod.rs b/programs/aidl/src/parser/mod.rs index 29b5eb0..b443b6b 100644 --- a/programs/aidl/src/parser/mod.rs +++ b/programs/aidl/src/parser/mod.rs @@ -1,11 +1,13 @@ +mod enumeration; mod expr; mod interface; mod structure; +mod types; use logos::{Lexer, Logos}; use crate::{ - ast::{IDLModule, Item, ItemAlias, ItemConstant, ItemInterface, ModulePath, UseDecl}, + ast::{IDLModule, Item, ItemAlias, ItemConstant, ModulePath, UseDecl}, lexer::{Ident, Span, Spanned, Token}, }; use std::iter::Iterator; @@ -52,7 +54,11 @@ impl<'a> TokenIterator<'a> { } pub fn current(&self) -> Spanned { - Spanned(self.lexer.slice().to_owned(), Span(self.lexer.span())) + Spanned(self.lexer.slice().to_owned(), self.span()) + } + + pub fn span(&self) -> Span { + Span(self.lexer.span()) } } @@ -94,11 +100,16 @@ impl<'a> Parser<'a> { } fn ask_ident(&mut self) -> Result, ParserError> { - Ok(crate::unwrap_match!( - self.get_real(|token| matches!(token, Token::Ident(Ident::Other(_))), "an identifier")?, - Spanned(Token::Ident(Ident::Other(ident)), span) => - Spanned(ident, span) - )) + Ok( + match self.get_real( + |token| matches!(token, Token::Ident(Ident::Other(_) | Ident::Underscore)), + "an identifier", + )? { + Spanned(Token::Ident(Ident::Other(ident)), span) => Spanned(ident, span), + Spanned(Token::Ident(Ident::Underscore), span) => Spanned("_".to_owned(), span), + _ => unreachable!(), + }, + ) } fn ask_modpath( @@ -115,18 +126,18 @@ impl<'a> Parser<'a> { Spanned(Token::Ident(Ident::Other(ident)), span_span) if !in_path_seg && waiting_next_seg => { - span = span + span_span; + span += span_span; segments.push(ident); in_path_seg = true; waiting_next_seg = false; } Spanned(Token::Dot, span_span) if in_path_seg && !waiting_next_seg => { - span = span + span_span; + span += span_span; waiting_next_seg = true; in_path_seg = false; } v if end(&v.0) && (in_path_seg || !waiting_next_seg) => { - span = span + v.1; + span += v.1; break; } _ => return Err(self.expected("a path segment")), @@ -136,22 +147,6 @@ impl<'a> Parser<'a> { Ok(Spanned(ModulePath { segments }, span)) } - fn _ask_interface(&mut self) -> Result, ParserError> { - let Spanned(_, kSp) = self.get_real( - |token| matches!(token, Token::Ident(Ident::Interface)), - "`Interface`", - )?; - let Spanned(ident, iSp) = self.ask_ident()?; - - Ok(Spanned::new( - ItemInterface { - name: ident, - functions: vec![], - }, - [kSp, iSp, self.semi()?], - )) - } - fn ask_alias(&mut self) -> Result, ParserError> { let Spanned(_, kSp) = self.get_real( |token| matches!(token, Token::Ident(Ident::Alias)), @@ -160,7 +155,7 @@ impl<'a> Parser<'a> { let Spanned(name, nSp) = self.ask_ident()?; let Spanned(_, eqSp) = self.get_real(|token| matches!(token, Token::Equals), "`=`")?; - let Spanned(referree, rSp) = self.ask_ident()?; + let Spanned(referree, rSp) = self.ask_type()?; Ok(Spanned::new( ItemAlias { name, referree }, @@ -177,7 +172,10 @@ impl<'a> Parser<'a> { let Spanned(_, eqSp) = self.get_real(|token| matches!(token, Token::Equals), "`=`")?; let Spanned(expr, exprSp) = self.ask_expr()?; - Ok(Spanned::new(ItemConstant { name, expr }, [kSp, nSp, eqSp, exprSp, self.semi()?])) + Ok(Spanned::new( + ItemConstant { name, expr }, + [kSp, nSp, eqSp, exprSp, self.semi()?], + )) } fn ask_item(&mut self) -> Result, ParserError> { @@ -186,10 +184,13 @@ impl<'a> Parser<'a> { Err(self.expected("a keyword, not just an identifier"))? } Token::Ident(keyword) => match keyword { - //Ident::Interface => self.ask_interface()?.map(Item::Interface), + Ident::Interface => self.ask_interface()?.map(Item::Interface), + Ident::Structure => self.ask_structure()?.map(Item::Structure), Ident::Alias => self.ask_alias()?.map(Item::Alias), Ident::Constant => self.ask_constant()?.map(Item::Constant), - _ => Err(self.expected("`Alias` or `Constant`"))?, + Ident::Function => self.ask_function()?.map(Item::Function), + Ident::Enumeration => self.ask_enumeration()?.map(Item::Enumeration), + _ => Err(self.expected("an item denoting keyword (Interface, Structure, Alias, Constant, Function, Enumeration)"))?, }, _ => Err(self.expected("a keyword"))?, }) diff --git a/programs/aidl/src/parser/structure.rs b/programs/aidl/src/parser/structure.rs index e69de29..32db118 100644 --- a/programs/aidl/src/parser/structure.rs +++ b/programs/aidl/src/parser/structure.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use crate::{lexer::{Ident, Spanned, Token}, ast::{Type, ItemStructure}}; + +use super::{Parser, ParserError}; + +impl<'a> Parser<'a> { + pub fn ask_structure(&mut self) -> Result, ParserError> { + let Spanned(_, span) = self.get_real( + |token| matches!(token, Token::Ident(Ident::Structure)), + "the `Structure` keyword", + )?; + + let Spanned(name, _) = self.ask_ident()?; + let Spanned(_, _) = self.get_real( + |token| matches!(token, Token::LeftCurly), + "an opening curly brace (`{`)", + )?; + + let mut fields = HashMap::::new(); + + loop { + match self.tokens.peek()?.0 { + Token::Ident(_) => { + let Spanned(ident, _) = self.ask_ident().unwrap(); + self.get_real(|token| matches!(token, Token::Colon), "a colon")?; + let Spanned(value, _) = self.ask_type()?; + fields.insert(ident, value); + if let Token::Comma = self.tokens.peek()?.0 { + self.eat(); + }; + } + Token::RightCurly => break, + _ => return Err(self.expected("an identifier or a closing curly brace (`}`)")), + } + } + + if let Spanned(Token::RightCurly, end) = self.tokens.next()? { + return Ok(Spanned(ItemStructure { name, fields }, span + end)); + }; + + Err(self.expected("closing curly braces")) + } +} diff --git a/programs/aidl/src/parser/types.rs b/programs/aidl/src/parser/types.rs new file mode 100644 index 0000000..27180c0 --- /dev/null +++ b/programs/aidl/src/parser/types.rs @@ -0,0 +1,45 @@ +use crate::{ + ast::{Type, TypeArguments, INFER_TYPE}, + lexer::{Spanned, Token}, +}; + +use super::{Parser, ParserError}; + +impl<'a> Parser<'a> { + pub fn ask_type(&mut self) -> Result, ParserError> { + let Spanned(name, span) = self.ask_ident()?; + + if name == INFER_TYPE { + return Ok(Spanned(Type::infer(), span)); + } + + let mut arguments = TypeArguments::None; + + if let Spanned(crate::lexer::Token::LeftArrow, _) = self.tokens.peek()? { + self.eat(); // eat `<` + + let mut args = vec![]; + + args.push(Box::new(self.ask_type()?.0)); + + if let Spanned(Token::Comma, _) = self.tokens.peek()? { + self.eat(); // skip comma, allow trailing comma + }; + + loop { + match self.tokens.peek()? { + Spanned(Token::Ident(_), _) => args.push(Box::new(self.ask_type()?.0)), + Spanned(Token::RightArrow, _) => { + self.eat(); + break; + } + _ => return Err(self.expected("closing angle brackets or a type name")), + } + } + + arguments = TypeArguments::AngleBracketed(args); + }; + + Ok(Spanned(Type { name, arguments }, span + self.tokens.span())) + } +}