diff --git a/build.rs b/build.rs index 3e8f09d..db4979d 100644 --- a/build.rs +++ b/build.rs @@ -11,16 +11,32 @@ fn main() { // FIXME(eddyb) streamline this process in `gll`. - // Find all the `.g` grammar fragments in `grammar/`. + // Find all the `.lyg` grammar fragments in `grammar/`. let fragments = WalkDir::new("grammar") .contents_first(true) .into_iter() .map(|entry| entry.unwrap()) - .filter(|entry| entry.path().extension().map_or(false, |ext| ext == "g")); + .filter(|entry| entry.path().extension().map_or(false, |ext| ext == "lyg")); // Start with the builtin rules for proc-macro grammars. let mut grammar = gll::proc_macro::builtin(); + // HACK(eddyb) inject a custom builtin - this should be upstreamed to gll! + { + use gll::proc_macro::{FlatTokenPat, Pat}; + + grammar.define( + "LIFETIME", + gll::grammar::eat(Pat(vec![ + FlatTokenPat::Punct { + ch: Some('\''), + joint: Some(true), + }, + FlatTokenPat::Ident(None), + ])), + ); + } + // Add in each grammar fragment to the grammar. for fragment in fragments { let path = fragment.into_path(); diff --git a/grammar/abi.lyg b/grammar/abi.lyg new file mode 100644 index 0000000..c2ea104 --- /dev/null +++ b/grammar/abi.lyg @@ -0,0 +1,31 @@ +// FIXME(eddyb) implement more specific literal support in `gll::proc_macro` +Abi = LITERAL?; + +/* +// HACK(eddyb) taken from `librustc_target/spec/abi.rs` +Abi = + // Defaults to "C" + | {} + // Platform-specific ABIs + | "\"cdecl\"" + | "\"stdcall\"" + | "\"fastcall\"" + | "\"vectorcall\"" + | "\"thiscall\"" + | "\"aapcs\"" + | "\"win64\"" + | "\"sysv64\"" + | "\"ptx-kernel\"" + | "\"msp430-interrupt\"" + | "\"x86-interrupt\"" + | "\"amdgpu-kernel\"" + // Cross-platform ABIs + | "\"Rust\"" + | "\"C\"" + | "\"system\"" + | "\"rust-intrinsic\"" + | "\"rust-call\"" + | "\"platform-intrinsic\"" + | "\"unadjusted\"" + ; +*/ diff --git a/grammar/attr.g b/grammar/attr.g deleted file mode 100644 index 89bce29..0000000 --- a/grammar/attr.g +++ /dev/null @@ -1,9 +0,0 @@ -OuterAttr = "#" attr:Attr; -InnerAttr = "#!" attr:Attr; -Attr = "[" path:Path input:AttrInput "]"; -AttrInput = - {} | - "=" LITERAL | - "(" TOKEN_TREE* ")" | - "[" TOKEN_TREE* "]" | - "{" TOKEN_TREE* "}"; diff --git a/grammar/attr.lyg b/grammar/attr.lyg new file mode 100644 index 0000000..3ecc104 --- /dev/null +++ b/grammar/attr.lyg @@ -0,0 +1,8 @@ +OuterAttr = "#" attr:Attr; +InnerAttr = "#" "!" attr:Attr; +Attr = "[" path:Path input:AttrInput "]"; +AttrInput = + | {} + | "=" TOKEN_TREE + | MacroInput + ; diff --git a/grammar/expr.lyg b/grammar/expr.lyg new file mode 100644 index 0000000..b8b7f2c --- /dev/null +++ b/grammar/expr.lyg @@ -0,0 +1,130 @@ +Expr = attrs:OuterAttr* kind:ExprKind; +ExprKind = + | Literal:LITERAL + | Paren:{ "(" attrs:InnerAttr* expr:Expr ")" } + | Borrow:{ "&" mutable:"mut"? expr:Expr } + | Box:{ "box" expr:Expr } + | Unary:{ op:UnaryOp expr:Expr } + | Try:{ expr:Expr "?" } + | Binary:{ left:Expr op:BinaryOp right:Expr } + | Assign:{ left:Expr { "=" | op:BinaryAssignOp } right:Expr } + | Range:{ start:Expr? ".." end:Expr? } + | RangeInclusive:{ start:Expr? "..=" end:Expr } + // unstable(type_ascription): + | Type:{ expr:Expr ":" ty:Type } + | Cast:{ expr:Expr "as" ty:Type } + | Field:{ base:Expr "." field:FieldName } + | Index:{ base:Expr "[" index:Expr "]" } + | Array:{ "[" attrs:InnerAttr* exprs:Expr* % "," ","? "]" } + | Repeat:{ "[" attrs:InnerAttr* elem:Expr ";" count:Expr "]" } + | Tuple:{ "(" attrs:InnerAttr* exprs:Expr* % "," ","? ")" } + | Path:QPath + | Call:{ callee:Expr "(" args:Expr* % "," ","? ")" } + | MethodCall:{ receiver:Expr "." method:PathSegment "(" args:Expr* % "," ","? ")" } + | Struct:{ path:Path "{" attrs:InnerAttr* fields:StructExprFieldsAndBase "}" } + | Block:{ { label:Label ":" }? unsafety:"unsafe"? block:Block } + // ustable(async_await): + | AsyncBlock:{ "async" block:Block } + // unstable(try_block): + | TryBlock:{ "try" block:Block } + | Continue:{ "continue" label:Label? } + | Break:{ "break" label:Label? value:Expr? } + | Return:{ "return" value:Expr? } + // unstable(generators): + | Yield:{ "yield" value:Expr? } + | If:If + | Match:{ "match" expr:Expr "{" attrs:InnerAttr* arms:MatchArm* "}" } + | Loop:{ { label:Label ":" }? "loop" body:Block } + | While:{ { label:Label ":" }? "while" cond:Cond body:Block } + | For:{ { label:Label ":" }? "for" pat:Pat "in" expr:Expr body:Block } + | Closure:{ + // unstable(generators): + generator_static:"static"? + // unstable(async_await): + asyncness:"async"? + by_val:"move"? + "|" args:ClosureArg* % "," ","? "|" { "->" ret_ty:Type }? body:Expr + } + | MacroCall:MacroCall + ; + +UnaryOp = + | Not:"!" + | Neg:"-" + | Deref:"*"; + +BinaryOp = + // Arithmetic & bitwise (these also have `BinaryAssignOp` forms) + | Add:"+" + | Sub:"-" + | Mul:"*" + | Div:"/" + | Rem:"%" + | BitXor:"^" + | BitAnd:"&" + | BitOr:"|" + | Shl:"<<" + | Shr:">>" + // Logic (short-circuiting) + | LogicAnd:"&&" + | LogicOr:"||" + // Comparisons + | Eq:"==" + | Lt:"<" + | Le:"<=" + | Ne:"!=" + | Gt:">" + | Ge:">=" + ; + +// FIXME(eddyb) figure out how to deduplicate this with `BinaryOp` +// The problem is something like `BinaryOp "="` allows a space in between, +// as the last token inside each `BinaryOp` case does not require being +// joint, but each token before the `=` here does impose that requirement. +BinaryAssignOp = + | Add:"+=" + | Sub:"-=" + | Mul:"*=" + | Div:"/=" + | Rem:"%=" + | BitXor:"^=" + | BitAnd:"&=" + | BitOr:"|=" + | Shl:"<<=" + | Shr:">>=" + ; + +FieldName = + | Ident:IDENT + // FIXME(eddyb) restrict this to only integer literals + | Numeric:LITERAL + ; + +// FIXME(eddyb) find a way to express this `A* B?` pattern better +StructExprFieldsAndBase = + | Fields:{ fields:StructExprField* % "," ","? } + | Base:{ ".." base:Expr } + | FieldsAndBase:{ fields:StructExprField+ % "," "," ".." base:Expr } + ; + +StructExprField = attrs:OuterAttr* kind:StructExprFieldKind; +StructExprFieldKind = + | Shorthand:IDENT + | Explicit:{ field:FieldName ":" expr:Expr } + ; + +Label = LIFETIME; + +If = "if" cond:Cond then:Block { "else" else_expr:ElseExpr }?; +Cond = + | Bool:Expr + | Let:{ "let" pat:Pat "=" expr:Expr } + ; +ElseExpr = + | Block:Block + | If:If + ; + +MatchArm = attrs:OuterAttr* "|"? pats:Pat+ % "|" { "if" guard:Expr }? "=>" body:Expr ","?; + +ClosureArg = pat:Pat { ":" ty:Type }?; diff --git a/grammar/generics.lyg b/grammar/generics.lyg new file mode 100644 index 0000000..b7c8c8b --- /dev/null +++ b/grammar/generics.lyg @@ -0,0 +1,42 @@ +Generics = "<" params:GenericParam* % "," ","? ">"; +GenericParam = attrs:OuterAttr* kind:GenericParamKind; +GenericParamKind = + | Lifetime:{ name:LIFETIME { ":" bounds:LifetimeBound* % "+" "+"? }? } + | Type:{ name:IDENT { ":" bounds:TypeBound* % "+" "+"? }? { "=" default:Type }? } + ; + +ForAllBinder = "for" generics:Generics; + +WhereClause = "where" bounds:WhereBound* % "," ","?; +WhereBound = + | Lifetime:{ lt:LIFETIME ":" bounds:LifetimeBound* % "+" "+"? } + | Type:{ binder:ForAllBinder? ty:Type ":" bounds:TypeBound* % "+" "+"? } + // unstable(#20041): + | TypeEq:{ binder:ForAllBinder? left:Type { "=" | "==" } right:Type } + ; + +LifetimeBound = outlives:LIFETIME; +TypeBound = + | Outlives:LIFETIME + | Trait:TypeTraitBound + | TraitParen:{ "(" bound:TypeTraitBound ")" } + ; +TypeTraitBound = unbound:"?"? binder:ForAllBinder? path:Path; + +GenericArgs = + | AngleBracket:{ "<" args_and_bindings:AngleBracketGenericArgsAndBindings? ","? ">" } + | Paren:{ "(" inputs:Type* % "," ","? ")" { "->" output:Type }? } + ; + +// FIXME(eddyb) find a way to express this `A* B*` pattern better +AngleBracketGenericArgsAndBindings = + | Args:GenericArg+ % "," + | Bindings:TypeBinding+ % "," + | ArgsAndBindings:{ args:GenericArg+ % "," "," bindings:TypeBinding+ % "," } + ; + +GenericArg = + | Lifetime:LIFETIME + | Type:Type + ; +TypeBinding = name:IDENT "=" ty:Type; diff --git a/grammar/item.g b/grammar/item.g deleted file mode 100644 index d5d5f7c..0000000 --- a/grammar/item.g +++ /dev/null @@ -1,7 +0,0 @@ -ModuleContents = attrs:InnerAttr* items:ItemWithOuterAttr*; - -ItemWithOuterAttr = attrs:OuterAttr* item:Item; -// TODO other items -Item = - ExternCrate:{ "extern" "crate" name:IDENT { "as" rename:IDENT }? ";" } | - Use:{ "use" path:Path { "as" rename:IDENT }? ";" }; // TODO use trees diff --git a/grammar/item.lyg b/grammar/item.lyg new file mode 100644 index 0000000..0691694 --- /dev/null +++ b/grammar/item.lyg @@ -0,0 +1,118 @@ +ModuleContents = attrs:InnerAttr* items:Item*; + +Item = attrs:OuterAttr* vis:Vis? kind:ItemKind; +ItemKind = + | Use:{ "use" use_tree:UseTree ";" } + | ExternCrate:{ "extern" "crate" name:IDENT { "as" rename:IDENT }? ";" } + | Mod:{ "mod" name:IDENT { ";" | "{" contents:ModuleContents "}" } } + | ForeignMod:{ "extern" abi:Abi "{" attrs:InnerAttr* items:ForeignItem* "}" } + | Static:{ "static" mutable:"mut"? name:IDENT ":" ty:Type "=" value:Expr ";" } + | Const:{ "const" name:IDENT ":" ty:Type "=" value:Expr ";" } + | Fn:{ header:FnHeader "fn" decl:FnDecl body:Block } + | TypeAlias:{ "type" name:IDENT generics:Generics? where_clause:WhereClause? "=" ty:Type ";" } + // unstable(existential_type): + | ExistentialType:{ "existential" "type" name:IDENT generics:Generics? where_clause:WhereClause? ":" bounds:TypeBound* % "+" "+"? ";" } + | Enum:{ "enum" name:IDENT generics:Generics? where_clause:WhereClause? "{" variants:EnumVariant* % "," ","? "}" } + | Struct:{ "struct" name:IDENT generics:Generics? body:StructBody } + | Union:{ "union" name:IDENT generics:Generics? where_clause:WhereClause? "{" fields:RecordField* % "," ","? "}" } + | Trait:{ + unsafety:"unsafe"? + // unstable(optin_builtin_traits): + auto:"auto"? + "trait" name:IDENT generics:Generics? + { ":" superbounds:TypeBound* % "+" "+"? }? + where_clause:WhereClause? "{" trait_items:TraitItem* "}" + } + // unstable(trait_alias): + | TraitAlias:{ + "trait" name:IDENT generics:Generics? + { ":" superbounds:TypeBound* % "+" "+"? }? + where_clause:WhereClause? "=" bounds:TypeBound* % "+" "+"? ";" + } + | Impl:{ + // unstable(specialization): + defaultness:"default"? + unsafety:"unsafe"? "impl" generics:Generics? + { negate:"!"? trait_path:Path "for" }? ty:Type + where_clause:WhereClause? "{" attrs:InnerAttr* impl_items:ImplItem* "}" + } + // unstable(decl_macro): + | Macro:{ "macro" name:IDENT { "(" TOKEN_TREE* ")" }? "{" TOKEN_TREE* "}" } + | MacroCall:ItemMacroCall + ; + +UseTree = + | Glob:{ prefix:UseTreePrefix? "*" } + | Nested:{ prefix:UseTreePrefix? "{" children:UseTree* % "," ","? "}" } + | Simple:{ path:Path { "as" rename:IDENT }? } + ; +UseTreePrefix = + | Path:{ path:Path "::" } + | Global:"::" + ; + +ForeignItem = attrs:OuterAttr* vis:Vis? kind:ForeignItemKind; +ForeignItemKind = + | Static:{ "static" mutable:"mut"? name:IDENT ":" ty:Type ";" } + | Fn:{ "fn" decl:FnDecl ";" } + // unstable(extern_types): + | Type:{ "type" name:IDENT ";" } + // unstable(macros_in_extern): + | MacroCall:ItemMacroCall + ; + +TraitItem = attrs:OuterAttr* kind:TraitItemKind; +TraitItemKind = + | Const:{ "const" name:IDENT ":" ty:Type { "=" default:Expr }? ";" } + | Fn:{ header:FnHeader "fn" decl:FnDecl { default_body:Block | ";" } } + | Type:{ "type" name:IDENT generics:Generics? { ":" bounds:TypeBound* % "+" "+"? }? where_clause:WhereClause? { "=" default:Type }? ";" } + | MacroCall:ItemMacroCall + ; + +ImplItem = + attrs:OuterAttr* + // unstable(specialization): + defaultness:"default"? + vis:Vis? kind:ImplItemKind + ; +ImplItemKind = + | Const:{ "const" name:IDENT ":" ty:Type "=" value:Expr ";" } + | Fn:{ header:FnHeader "fn" decl:FnDecl body:Block } + | Type:{ "type" name:IDENT generics:Generics? where_clause:WhereClause? "=" ty:Type ";" } + // unstable(existential_type): + | ExistentialType:{ "existential" "type" name:IDENT generics:Generics? where_clause:WhereClause? ":" bounds:TypeBound* % "+" "+"? ";" } + | MacroCall:ItemMacroCall + ; + +FnHeader = constness:"const"? unsafety:"unsafe"? asyncness:"async"? { "extern" abi:Abi }?; +FnDecl = name:IDENT generics:Generics? "(" args:FnArgs? ","? ")" { "->" ret_ty:Type }? where_clause:WhereClause?; + +// FIXME(eddyb) find a way to express this `A* B?` pattern better +FnArgs = + | Regular:FnArg+ % "," + | Variadic:"..." + | RegularAndVariadic:{ args:FnArg+ % "," "," "..." } + ; +FnArg = + | SelfValue:{ mutable:"mut"? "self" } + | SelfRef:{ "&" lt:LIFETIME? mutable:"mut"? "self" } + | Regular:FnSigInput + ; + +EnumVariant = attrs:OuterAttr* name:IDENT kind:EnumVariantKind { "=" discr:Expr }?; +EnumVariantKind = + | Unit:{} + | Tuple:{ "(" fields:TupleField* % "," ","? ")" } + | Record:{ "{" fields:RecordField* % "," ","? "}" } + ; + +// FIXME(eddyb) could maybe be shared more with `EnumVariantKind`? +// The problem is the semicolons for `Unit` and `Tuple`, and the where clauses. +StructBody = + | Unit:{ where_clause:WhereClause? ";" } + | Tuple:{ "(" fields:TupleField* % "," ","? ")" where_clause:WhereClause? ";" } + | Record:{ where_clause:WhereClause? "{" fields:RecordField* % "," ","? "}" } + ; + +TupleField = attrs:OuterAttr* vis:Vis? ty:Type; +RecordField = attrs:OuterAttr* vis:Vis? name:IDENT ":" ty:Type; diff --git a/grammar/macro.lyg b/grammar/macro.lyg new file mode 100644 index 0000000..87072b4 --- /dev/null +++ b/grammar/macro.lyg @@ -0,0 +1,15 @@ +MacroCall = path:Path "!" ident_input:IDENT? input:MacroInput; +MacroInput = + | "(" TOKEN_TREE* ")" + | "[" TOKEN_TREE* "]" + | "{" TOKEN_TREE* "}" + ; + +// FIXME(eddyb) could maybe be shared more with `MacroInput`? +// The problem is the semicolons for the `()` and `[]` cases. +ItemMacroCall = path:Path "!" ident_input:IDENT? input:ItemMacroInput; +ItemMacroInput = + | "(" TOKEN_TREE* ")" ";" + | "[" TOKEN_TREE* "]" ";" + | "{" TOKEN_TREE* "}" + ; diff --git a/grammar/pat.lyg b/grammar/pat.lyg new file mode 100644 index 0000000..8442949 --- /dev/null +++ b/grammar/pat.lyg @@ -0,0 +1,47 @@ +Pat = + | Wild:"_" + | Literal:{ minus:"-"? lit:LITERAL } + // unstable(exclusive_range_pattern): + | Range:{ start:PatRangeValue ".." end:PatRangeValue } + | RangeInclusive:{ start:PatRangeValue { "..." | "..=" } end:PatRangeValue } + | Binding:{ binding:Binding { "@" subpat:Pat }? } + | Paren:{ "(" pat:Pat ")" } + | Ref:{ "&" mutable:"mut"? pat:Pat } + // unstable(box_patterns): + | Box:{ "box" pat:Pat } + | Slice:{ "[" elems:SlicePatElem* % "," ","? "]" } + | Tuple:{ "(" fields:TuplePatField* % "," ","? ")" } + | Path:QPath + | TupleStruct:{ path:Path "(" fields:TuplePatField* % "," ","? ")" } + | Struct:{ path:Path "{" fields:StructPatFieldsAndEllipsis "}" } + | MacroCall:MacroCall + ; + +PatRangeValue = + | Literal:{ minus:"-"? lit:LITERAL } + | Path:QPath + ; + +Binding = boxed:"box"? reference:"ref"? mutable:"mut"? name:IDENT; + +SlicePatElem = + | Subslice:{ subpat:Pat? ".." } + | Pat:Pat + ; + +TuplePatField = + | Ellipsis:".." + | Pat:Pat + ; + +// FIXME(eddyb) find a way to express this `A* B?` pattern better +StructPatFieldsAndEllipsis = + | Fields:{ fields:StructPatField* % "," ","? } + | Ellipsis:{ ".." } + | FieldsAndEllipsis:{ fields:StructPatField+ % "," "," ".." } + ; + +StructPatField = + | Shorthand:Binding + | Explicit:{ field:FieldName ":" pat:Pat } + ; diff --git a/grammar/path.g b/grammar/path.g deleted file mode 100644 index b880d1a..0000000 --- a/grammar/path.g +++ /dev/null @@ -1,2 +0,0 @@ -Path = global:"::"? segments:PathSegment* % "::"; -PathSegment = ident:IDENT; // TODO generics diff --git a/grammar/path.lyg b/grammar/path.lyg new file mode 100644 index 0000000..10de3e1 --- /dev/null +++ b/grammar/path.lyg @@ -0,0 +1,9 @@ +Path = global:"::"? path:RelativePath; +RelativePath = segments:PathSegment+ % "::"; +PathSegment = ident:IDENT { "::"? args:GenericArgs }?; + +QSelf = "<" ty:Type { "as" trait_path:Path }? ">"; +QPath = + | Unqualified:Path + | Qualified:{ qself:QSelf "::" path:RelativePath } + ; diff --git a/grammar/stmt.lyg b/grammar/stmt.lyg new file mode 100644 index 0000000..d0e2e48 --- /dev/null +++ b/grammar/stmt.lyg @@ -0,0 +1,8 @@ +Stmt = + | Item:Item + | Local:{ attrs:OuterAttr* "let" pat:Pat { ":" ty:Type }? { "=" init:Expr }? ";" } + | Expr:Expr + | Semi:";" + ; + +Block = "{" attrs:InnerAttr* Stmt* "}"; diff --git a/grammar/type.lyg b/grammar/type.lyg new file mode 100644 index 0000000..124fb23 --- /dev/null +++ b/grammar/type.lyg @@ -0,0 +1,27 @@ +Type = + | Infer:"_" + | Never:"!" + | Paren:{ "(" ty:Type ")" } + | RawPtr:{ "*" { "const" | mutable:"mut" } pointee:Type } + | Ref:{ "&" lt:LIFETIME? mutable:"mut"? pointee:Type } + | Slice:{ "[" elem:Type "]" } + | Array:{ "[" elem:Type ";" len:Expr "]" } + | Tuple:{ "(" tys:Type* % "," ","? ")" } + | Path:QPath + | FnPtr:{ + binder:ForAllBinder? unsafety:"unsafe"? { "extern" abi:Abi }? + "fn" "(" inputs:FnSigInputs? ","? ")" { "->" ret_ty:Type }? + } + | ImplTrait:{ "impl" bounds:TypeBound+ % "+" "+"? } + | DynTrait:{ "dyn"? bounds:TypeBound+ % "+" "+"? } + // unstable(not exposed to users): + | TypeOf:{ "typeof" "(" expr:Expr ")" } + | MacroCall:MacroCall + ; + +FnSigInputs = + | Regular:FnSigInput+ % "," + | Variadic:"..." + | RegularAndVariadic:{ inputs:FnSigInput+ % "," "," "..." } + ; +FnSigInput = { pat:Pat ":" }? ty:Type; diff --git a/grammar/vis.lyg b/grammar/vis.lyg new file mode 100644 index 0000000..1d3d3f3 --- /dev/null +++ b/grammar/vis.lyg @@ -0,0 +1,12 @@ +Vis = + // unstable(crate_visibility_modifier): + | Crate:"crate" + | Pub:"pub" + | Restricted:{ "pub" "(" restriction:VisRestriction ")" } + ; +VisRestriction = + | Crate:"crate" + | Self_:"self" + | Super:"super" + | Path:{ "in" path:Path } + ; diff --git a/src/bin/tester.rs b/src/bin/tester.rs index 059ec6f..c7fd25c 100644 --- a/src/bin/tester.rs +++ b/src/bin/tester.rs @@ -68,12 +68,37 @@ fn parse_file_with(path: &Path, f: impl FnOnce(ModuleContentsResult) -> R) -> /// Output the result of a single file to stderr, /// optionally prefixed by a given `path`. -fn report_file_result(path: Option<&Path>, result: ModuleContentsResult) { +fn report_file_result( + path: Option<&Path>, + result: ModuleContentsResult, + ambiguity_result: Result<(), MoreThanOne>, +) { if let Some(path) = path { eprint!("{}: ", path.display()); } - // FIXME(eddyb) when we start parsing more this could become quite noisy. - eprintln!("{:#?}", result); + // Avoid printing too much, especially not any parse nodes. + match (result, ambiguity_result) { + (Ok(_), Ok(_)) => eprintln!("OK"), + (Ok(_), Err(_)) => eprintln!("OK (ambiguous)"), + (Err(parse::ParseError::TooShort(handle)), _) => { + eprint!("FAIL after "); + + #[cfg(procmacro2_semver_exempt)] + { + // HACK(eddyb) work around `proc-macro2` `Span` printing limitation + let end_location = handle.source_info().end.end(); + eprintln!("{}:{}", end_location.line, end_location.column); + } + #[cfg(not(procmacro2_semver_exempt))] + { + eprintln!( + "(missing location information; \ + set `RUSTFLAGS='--cfg procmacro2_semver_exempt'`)" + ); + } + } + (Err(parse::ParseError::NoParse), _) => eprintln!("FAIL (lexer error?)"), + } } fn ambiguity_check(handle: ModuleContentsHandle) -> Result<(), MoreThanOne> { @@ -118,8 +143,11 @@ fn main() { } => { // Not much to do, try to parse the file and report the result. parse_file_with(&file, |result| { + let mut ambiguity_result = Ok(()); match result { Ok(handle) | Err(parse::ParseError::TooShort(handle)) => { + ambiguity_result = ambiguity_check(handle); + if let Some(out_path) = graphviz_forest { handle .parser @@ -130,7 +158,7 @@ fn main() { } Err(parse::ParseError::NoParse) => {} } - report_file_result(None, result); + report_file_result(None, result, ambiguity_result); }); } Command::Dir { verbose, dir } => { @@ -148,35 +176,67 @@ fn main() { .map(|entry| entry.unwrap()) .filter(|entry| entry.path().extension().map_or(false, |ext| ext == "rs")); + let mut stdout = io::stdout(); + // Go through all the files and try to parse each of them. for file in files { let path = file.into_path(); + + total_count += 1; + if !verbose { + // Limit the compact output to 80 columns wide. + if total_count % 80 == 0 { + println!(""); + } + } + + // HACK(eddyb) avoid parsing some files that hit + // `lykenware/gll` worst-cases (many GBs of RAM usage) + // FIXME(eddyb) fix the problems (e.g. implement GC). + const BLACKLIST: &[&str] = &[ + "libcore/unicode/tables.rs", + "issues/issue-29466.rs", + "issues/issue-29227.rs", + ]; + if BLACKLIST.iter().any(|&b| path.ends_with(b)) { + if verbose { + eprintln!("{}: SKIP (blacklisted)...", path.display()); + } else { + print!("S"); + stdout.flush().unwrap(); + } + continue; + } + + // Indicate the current file being parsed in verbose mode. + // This can be used to find files to blacklist (see above). + if verbose { + eprint!("{}...\r", path.display()); + } + parse_file_with(&path, |result| { // Increment counters and figure out the character to print. + let mut ambiguity_result = Ok(()); let (status, count) = match result { Ok(handle) => { - if ambiguity_check(handle).is_ok() { - ('~', &mut unambiguous_count) + ambiguity_result = ambiguity_check(handle); + if ambiguity_result.is_ok() { + ('.', &mut unambiguous_count) } else { - ('!', &mut ambiguous_count) + ('-', &mut ambiguous_count) } } - Err(parse::ParseError::TooShort(_)) => ('.', &mut too_short_count), - Err(parse::ParseError::NoParse) => ('X', &mut no_parse_count), + Err(parse::ParseError::TooShort(_)) => ('X', &mut too_short_count), + Err(parse::ParseError::NoParse) => ('L', &mut no_parse_count), }; *count += 1; - total_count += 1; if verbose { // Unless we're in verbose mode, in which case we print more. - report_file_result(Some(&path), result); + report_file_result(Some(&path), result, ambiguity_result); } else { - // Limit the compact output to 80 columns wide. - if total_count % 80 == 0 { - println!(""); - } print!("{}", status); - io::stdout().flush().unwrap(); + stdout.flush().unwrap(); } }) } @@ -187,7 +247,7 @@ fn main() { println!("* {} parsed fully and unambiguously", unambiguous_count); println!("* {} parsed fully (but ambiguously)", ambiguous_count); println!("* {} parsed partially (only a prefix)", too_short_count); - println!("* {} didn't parse at all", no_parse_count); + println!("* {} didn't parse at all (lexer error?)", no_parse_count); } } }