diff --git a/programs/aidl/Cargo.toml b/programs/aidl/Cargo.toml index 7616c2c..75503f4 100644 --- a/programs/aidl/Cargo.toml +++ b/programs/aidl/Cargo.toml @@ -7,5 +7,9 @@ edition = "2021" [dependencies] codespan-reporting = "0.11.1" +derive_more = "0.99.17" logos = "0" +proc-macro2 = "1.0.56" +quote = "1.0.26" +syn = "2.0.15" thiserror = "1" diff --git a/programs/aidl/assets/core.idl b/programs/aidl/assets/core.idl index 1067197..330598f 100644 --- a/programs/aidl/assets/core.idl +++ b/programs/aidl/assets/core.idl @@ -15,7 +15,7 @@ Enumeration Option { } Structure Version { - major: Byte, - minor: Byte, - patch: Byte, + major: Byte, + minor: Byte, + patch: Byte, } diff --git a/programs/aidl/assets/error_example.idl b/programs/aidl/assets/error_example.idl new file mode 100644 index 0000000..6fbd52c --- /dev/null +++ b/programs/aidl/assets/error_example.idl @@ -0,0 +1,19 @@ +Type UUID = Array; + +Type Nanoseconds = U32; + +Structure Duration { + secs: U64 + nanos: Nanoseconds +} + +Structure LinkedList { + data: T, + child: Option>, +} + +Constant VERSION = Version { + major: 1, + minor: 0, + patch: 0 +}; diff --git a/programs/aidl/assets/examples.idl b/programs/aidl/assets/examples.idl deleted file mode 100644 index b4070ea..0000000 --- a/programs/aidl/assets/examples.idl +++ /dev/null @@ -1,13 +0,0 @@ -Type UUID = Array[U8; 16]; - -Type Nanoseconds = U32; - -Structure Duration{ - secs: U64, - nanos: Nanoseconds, -} - -Structure LinkedList{ - data: Any, - child: Option, -} \ No newline at end of file diff --git a/programs/aidl/assets/vfs.idl b/programs/aidl/assets/vfs.idl index c9131ad..10d0a51 100644 --- a/programs/aidl/assets/vfs.idl +++ b/programs/aidl/assets/vfs.idl @@ -1,11 +1,11 @@ // core provides lots of useful types like String and Byte Use core; -Constant VERSION = Version { +Constant VERSION = Make Version { major: 1, minor: 0, patch: 0, -} +}; Alias Path = String; @@ -15,10 +15,10 @@ Structure File { } Interface File { - function new accepts(Path) returns(None); + Function new Takes(Path) Returns(None); // Open in this iteration assumes the file exists - function open accepts(Path) returns(File); + Function open Takes(Path) Returns(File); - function close accepts(File) returns(None); + Function close Takes(File) Returns(None); } diff --git a/programs/aidl/src/ast.rs b/programs/aidl/src/ast.rs index 78b7ee3..3ffa60d 100644 --- a/programs/aidl/src/ast.rs +++ b/programs/aidl/src/ast.rs @@ -22,7 +22,7 @@ pub enum Item { Constant(ItemConstant), Function(Function), Structure(ItemStructure), - Enumeration(ItemEnumeration) + Enumeration(ItemEnumeration), } #[derive(Debug, Default)] @@ -42,7 +42,7 @@ impl Type { pub fn infer() -> Self { Self { name: String::from(INFER_TYPE), - arguments: TypeArguments::None + arguments: TypeArguments::None, } } } @@ -82,6 +82,7 @@ pub struct ItemInterface { pub struct ItemStructure { pub name: String, pub fields: HashMap, + pub arguments: TypeArguments, } #[derive(Debug)] @@ -100,13 +101,13 @@ pub struct ItemConstant { pub struct ItemEnumeration { pub name: String, pub arguments: TypeArguments, - pub variants: Vec + pub variants: Vec, } #[derive(Debug)] pub struct EnumerationVariant { pub name: String, - pub content: EnumerationContent + pub content: EnumerationContent, } #[derive(Debug, Default)] @@ -115,7 +116,7 @@ pub enum EnumerationContent { None, Tuple(Vec), Structure(HashMap), - Value(NumberLiteral) + Value(NumberLiteral), } #[derive(Debug)] @@ -143,22 +144,32 @@ pub enum Literal { Char(char), } -#[derive(Debug)] +#[derive(Debug, derive_more::Display)] pub enum NumberLiteral { + #[display(fmt = "{_0}ptr")] Ptr(usize), + #[display(fmt = "{_0}u8")] U8(u8), + #[display(fmt = "{_0}i8")] I8(i8), + #[display(fmt = "{_0}u16")] U16(u16), + #[display(fmt = "{_0}i16")] I16(i16), + #[display(fmt = "{_0}u32")] U32(u32), + #[display(fmt = "{_0}i32")] I32(i32), + #[display(fmt = "{_0}u64")] U64(u64), + #[display(fmt = "{_0}i64")] I64(i64), + #[display(fmt = "{_0}")] Infer(i64), } diff --git a/programs/aidl/src/codegen.rs b/programs/aidl/src/codegen.rs new file mode 100644 index 0000000..6baf426 --- /dev/null +++ b/programs/aidl/src/codegen.rs @@ -0,0 +1,10 @@ +use std::path::Path; + +use crate::ast::IDLModule; + +use proc_macro2::{TokenStream, Ident, Span}; +use quote::quote; + +pub fn generate(module: IDLModule) -> TokenStream { + quote! {} +} diff --git a/programs/aidl/src/lexer.rs b/programs/aidl/src/lexer.rs index 0e11bd6..1fa8e9e 100644 --- a/programs/aidl/src/lexer.rs +++ b/programs/aidl/src/lexer.rs @@ -5,119 +5,175 @@ use std::{ use logos::Logos; -#[derive(Logos, Debug, PartialEq)] +#[derive(Logos, Debug, PartialEq, derive_more::Display, Clone)] #[logos(skip r"[ \t\n\f]+")] pub enum Token { #[token("{")] + #[display(fmt = "{{")] LeftCurly, #[token("}")] + #[display(fmt = "}}")] RightCurly, #[token("(")] + #[display(fmt = "(")] LeftParen, #[token(")")] + #[display(fmt = ")")] RightParen, #[token(";")] + #[display(fmt = ";")] Semicolon, #[token(":")] + #[display(fmt = ":")] Colon, #[token("<")] + #[display(fmt = "<")] LeftArrow, #[token(">")] + #[display(fmt = ">")] RightArrow, #[token(",")] + #[display(fmt = ",")] Comma, #[token("=")] + #[display(fmt = "=")] Equals, #[token(".")] + #[display(fmt = ".")] Dot, // why #[regex("\"(?s:[^\"\\\\]|\\\\.)*\"", |lex| lex.slice().strip_prefix('"')?.strip_suffix('"').map(ToOwned::to_owned))] + #[display(fmt = "\"{_0}\"")] StringLiteral(String), #[regex(r"'.'", |lex| lex.slice().strip_prefix('\'')?.strip_suffix('\'')?.parse().ok())] + #[display(fmt = "{_0}")] CharLiteral(char), #[regex(r#"(-)?\d+"#, |lex| lex.slice().parse().ok())] + #[display(fmt = "{_0}")] NumberLiteral(i64), #[regex( - r"(ptr|u8|i8|u16|i16|u32|i32|u64|i64|f32|f64)", + "(ptr|u8|i8|u16|i16|u32|i32|u64|i64|f32|f64)", |lex| NumberSuffix::lexer(lex.slice()).next().and_then(Result::ok) )] + #[display(fmt = "{_0}")] NumberSuffix(NumberSuffix), #[regex(r#"[a-zA-Z_][a-zA-Z\d_]*"#, |lex| Ident::lexer(lex.slice()).next().and_then(Result::ok))] + #[display(fmt = "{_0}")] Ident(Ident), #[regex(r"//.*", |lex| lex.slice().parse().ok())] + #[display(fmt = "//{_0}")] Comment(String), } -#[derive(Logos, Debug, PartialEq, Eq)] +#[derive(Logos, Debug, Clone, PartialEq, Eq, derive_more::Display)] pub enum Ident { #[token("Interface")] + #[display(fmt = "Interface")] Interface, #[token("Function")] + #[display(fmt = "Function")] Function, #[token("Constant")] + #[display(fmt = "Constant")] Constant, #[token("Structure")] + #[display(fmt = "Structure")] Structure, #[token("Alias")] + #[display(fmt = "Alias")] Alias, #[token("Enumeration")] + #[display(fmt = "Enumeration")] Enumeration, #[token("Use")] + #[display(fmt = "Use")] Use, #[token("Make")] + #[display(fmt = "Make")] Make, #[token("Takes")] + #[display(fmt = "Takes")] Takes, #[token("Returns")] + #[display(fmt = "Returns")] Returns, #[token("_")] + #[display(fmt = "_")] Underscore, #[regex(r"[a-zA-Z_][a-zA-Z\d_]*", |lex| lex.slice().parse().ok())] + #[display(fmt = "{_0}")] Other(String), } -#[derive(Logos, Debug, PartialEq, Eq)] +#[derive(Logos, Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] pub enum NumberSuffix { - #[token("Ptr")] + #[token("ptr")] + #[display(fmt = "ptr")] Ptr, #[token("u8")] + #[display(fmt = "u8")] U8, #[token("i8")] + #[display(fmt = "i8")] I8, #[token("u16")] + #[display(fmt = "u16")] U16, #[token("i16")] + #[display(fmt = "i16")] I16, #[token("u32")] + #[display(fmt = "u32")] U32, #[token("i32")] + #[display(fmt = "i32")] I32, #[token("u64")] + #[display(fmt = "u64")] U64, #[token("i64")] + #[display(fmt = "i64")] I64, #[token("f32")] + #[display(fmt = "f32")] F32, #[token("f64")] + #[display(fmt = "f64")] F64, } +impl NumberSuffix { + pub const ALL_SUFFIXES: [&str; 11] = [ + "ptr", + "u8", + "i8", + "u16", + "i16", + "u32", + "i32", + "u64", + "i64", + "f32", + "f64" + ]; +} + #[derive(Debug, Clone)] pub struct Span(pub Range); impl Span { diff --git a/programs/aidl/src/main.rs b/programs/aidl/src/main.rs index 62527ba..10301d4 100644 --- a/programs/aidl/src/main.rs +++ b/programs/aidl/src/main.rs @@ -1,12 +1,20 @@ #![feature(result_option_inspect)] #![allow(non_snake_case)] -use std::path::Path; +use std::{fmt::Display, path::Path, process::exit}; use codespan_reporting::{ - diagnostic::Label, - term::{termcolor::StandardStream, Config}, + diagnostic::{Diagnostic, Label, Severity}, + files::SimpleFile, + term::{ + emit, + termcolor::{ColorSpec, StandardStream, StandardStreamLock}, + Config, Styles, + }, }; +use lexer::{NumberSuffix, Token}; +use logos::Logos; +use parser::TokenIterator; use crate::lexer::Spanned; @@ -16,20 +24,95 @@ mod parser; //const TEST: &str = include_str!("../assets/why.idl"); +fn precheck>( + writer: &mut StandardStreamLock<'_>, + config: &Config, + file: &SimpleFile, +) { + let mut lexer = TokenIterator::new(file.source().as_ref()); + let mut diagnostics = vec![]; + let mut previous = lexer.peek().ok().map(|Spanned(a, b)| Spanned(a.clone(), b)); + + while let Ok(Spanned(token, span)) = lexer.next() { + let prev = Spanned(token.clone(), span.clone()); + match token { + Token::Ident(lexer::Ident::Other(t)) if t == "Type" => { + diagnostics.push( + Diagnostic::error() + .with_labels(vec![Label::primary((), span.0)]) + .with_message("`Type` is not supported anymore.") + .with_notes(vec!["use `Alias` instead of `Type`".into()]), + ); + } + Token::Ident(lexer::Ident::Other(ident)) + if lexer + .peek() + .is_ok_and(|Spanned(a, _)| matches!(a, Token::LeftCurly)) + && previous.is_some_and(|Spanned(ref a, _)| matches!(a, Token::Equals)) => + { + diagnostics.push( + Diagnostic::error() + .with_message("Unknown expression") + .with_labels(vec![Label::primary((), span.0.clone())]) + .with_notes(vec![ + format!("add `Make` before the structure name to create a Make expression that will construct the `{ident}` structure" + ), + ]), + ); + } + Token::Ident(lexer::Ident::Other(ident)) + if NumberSuffix::ALL_SUFFIXES.contains(&ident.to_lowercase().as_str()) => + { + diagnostics.push( + Diagnostic::warning() + .with_message("Potentially invalid use of an uppercased number type") + .with_labels(vec![Label::primary((), span.0)]) + .with_notes(vec![format!( + "Replace {ident} with {}", + ident.to_lowercase() + ), "Code generation might fail".into()]), + ); + } + _ => {} + } + previous = Some(prev); + } + + if !diagnostics.is_empty() { + let mut was_fatal = false; + for diagnostic in diagnostics { + if let Severity::Error | Severity::Bug = &diagnostic.severity { + was_fatal = true; + } + + emit(writer, config, file, &diagnostic).unwrap(); + } + + if was_fatal { + exit(1); + } + } +} + fn main() { let mut args = std::env::args(); args.next().unwrap(); if let Some(file) = args.next() { let path = Path::new(&file); - dbg!(path); let codespan_file = codespan_reporting::files::SimpleFile::new( &file, std::fs::read_to_string(path).unwrap(), ); let writer = StandardStream::stdout(codespan_reporting::term::termcolor::ColorChoice::Auto); - let config = Config::default(); + let config = Config { + tab_width: 2, + ..Default::default() + }; + + precheck(&mut writer.lock(), &config, &codespan_file); + match parser::parse(codespan_file.source()) { - Ok(ast) => println!("SUCCESS: \n{:#?}", ast), + Ok(ast) => println!("{:#?}", ast), Err(e) => { let msg = e.to_string(); let label = match e { diff --git a/programs/aidl/src/parser/mod.rs b/programs/aidl/src/parser/mod.rs index b443b6b..a1f0b0a 100644 --- a/programs/aidl/src/parser/mod.rs +++ b/programs/aidl/src/parser/mod.rs @@ -12,7 +12,7 @@ use crate::{ }; use std::iter::Iterator; -struct TokenIterator<'a> { +pub struct TokenIterator<'a> { lexer: Lexer<'a, Token>, peeked: Option>, } @@ -102,11 +102,17 @@ impl<'a> Parser<'a> { fn ask_ident(&mut self) -> Result, ParserError> { Ok( match self.get_real( - |token| matches!(token, Token::Ident(Ident::Other(_) | Ident::Underscore)), + |token| { + matches!( + token, + Token::Ident(Ident::Other(_) | Ident::Underscore) | Token::NumberSuffix(_) + ) + }, "an identifier", )? { Spanned(Token::Ident(Ident::Other(ident)), span) => Spanned(ident, span), Spanned(Token::Ident(Ident::Underscore), span) => Spanned("_".to_owned(), span), + Spanned(Token::NumberSuffix(suffix), span) => Spanned(suffix.to_string(), span), _ => unreachable!(), }, ) diff --git a/programs/aidl/src/parser/structure.rs b/programs/aidl/src/parser/structure.rs index 32db118..14a82fa 100644 --- a/programs/aidl/src/parser/structure.rs +++ b/programs/aidl/src/parser/structure.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::{lexer::{Ident, Spanned, Token}, ast::{Type, ItemStructure}}; +use crate::{ + ast::{ItemStructure, Type}, + lexer::{Ident, Spanned, Token}, +}; use super::{Parser, ParserError}; @@ -11,7 +14,7 @@ impl<'a> Parser<'a> { "the `Structure` keyword", )?; - let Spanned(name, _) = self.ask_ident()?; + let Spanned(Type { name, arguments }, _) = self.ask_type()?; let Spanned(_, _) = self.get_real( |token| matches!(token, Token::LeftCurly), "an opening curly brace (`{`)", @@ -36,7 +39,14 @@ impl<'a> Parser<'a> { } if let Spanned(Token::RightCurly, end) = self.tokens.next()? { - return Ok(Spanned(ItemStructure { name, fields }, span + end)); + return Ok(Spanned( + ItemStructure { + name, + fields, + arguments, + }, + span + end, + )); }; Err(self.expected("closing curly braces")) diff --git a/programs/aidl/src/parser/types.rs b/programs/aidl/src/parser/types.rs index 27180c0..5afbefa 100644 --- a/programs/aidl/src/parser/types.rs +++ b/programs/aidl/src/parser/types.rs @@ -7,6 +7,17 @@ use super::{Parser, ParserError}; impl<'a> Parser<'a> { pub fn ask_type(&mut self) -> Result, ParserError> { + if let Spanned(Token::NumberLiteral(_), _) = self.tokens.peek()? { + let Spanned(number, span) = self._ask_number_literal()?; + return Ok(Spanned( + Type { + name: number.to_string(), + arguments: TypeArguments::None, + }, + span, + )); + }; + let Spanned(name, span) = self.ask_ident()?; if name == INFER_TYPE { @@ -28,7 +39,9 @@ impl<'a> Parser<'a> { loop { match self.tokens.peek()? { - Spanned(Token::Ident(_), _) => args.push(Box::new(self.ask_type()?.0)), + Spanned(Token::Ident(_) | Token::NumberLiteral(_), _) => { + args.push(Box::new(self.ask_type()?.0)) + } Spanned(Token::RightArrow, _) => { self.eat(); break;