1+ use std:: borrow:: Cow ;
2+ use serde_json:: Value ;
3+ use crate :: cache:: Cache ;
4+
5+ #[ derive( Debug ) ]
6+ pub struct Command {
7+ pub kind : CommandKind ,
8+ pub path : String ,
9+ pub lineno : usize ,
10+ }
11+
12+ #[ derive( Debug ) ]
13+ pub enum CommandKind {
14+ /// `//@ has <path>`
15+ ///
16+ /// Checks the path exists.
17+ HasPath ,
18+
19+ /// `//@ has <path> <value>`
20+ ///
21+ /// Check one thing at the path is equal to the value.
22+ HasValue { value : String } ,
23+
24+ /// `//@ !has <path>`
25+ ///
26+ /// Checks the path doesn't exist.
27+ HasNotPath ,
28+
29+ /// `//@ !has <path> <value>`
30+ ///
31+ /// Checks the path exists, but doesn't have the given value.
32+ HasNotValue { value : String } ,
33+
34+ /// `//@ is <path> <value>`
35+ ///
36+ /// Check the path is the given value.
37+ Is { value : String } ,
38+
39+ /// `//@ is <path> <value> <value>...`
40+ ///
41+ /// Check that the path matches to exactly every given value.
42+ IsMany { values : Vec < String > } ,
43+
44+ /// `//@ !is <path> <value>`
45+ ///
46+ /// Check the path isn't the given value.
47+ IsNot { value : String } ,
48+
49+ /// `//@ count <path> <value>`
50+ ///
51+ /// Check the path has the expected number of matches.
52+ CountIs { expected : usize } ,
53+
54+ /// `//@ set <name> = <path>`
55+ Set { variable : String } ,
56+ }
57+
58+ impl CommandKind {
59+ /// Returns both the kind and the path.
60+ ///
61+ /// Returns `None` if the command isn't from jsondocck (e.g. from compiletest).
62+ pub fn parse < ' a > (
63+ command_name : & str ,
64+ negated : bool ,
65+ args : & ' a [ String ] ,
66+ ) -> Option < ( Self , & ' a str ) > {
67+ let kind = match ( command_name, negated) {
68+ ( "count" , false ) => {
69+ assert_eq ! ( args. len( ) , 2 ) ;
70+ let expected = args[ 1 ] . parse ( ) . expect ( "invalid number for `count`" ) ;
71+ Self :: CountIs { expected }
72+ }
73+
74+ ( "ismany" , false ) => {
75+ // FIXME: Make this >= 3, and migrate len(values)==1 cases to @is
76+ assert ! ( args. len( ) >= 2 , "Not enough args to `ismany`" ) ;
77+ let values = args[ 1 ..] . to_owned ( ) ;
78+ Self :: IsMany { values }
79+ }
80+
81+ ( "is" , false ) => {
82+ assert_eq ! ( args. len( ) , 2 ) ;
83+ Self :: Is { value : args[ 1 ] . clone ( ) }
84+ }
85+ ( "is" , true ) => {
86+ assert_eq ! ( args. len( ) , 2 ) ;
87+ Self :: IsNot { value : args[ 1 ] . clone ( ) }
88+ }
89+
90+ ( "set" , false ) => {
91+ assert_eq ! ( args. len( ) , 3 ) ;
92+ assert_eq ! ( args[ 1 ] , "=" ) ;
93+ return Some ( ( Self :: Set { variable : args[ 0 ] . clone ( ) } , & args[ 2 ] ) ) ;
94+ }
95+
96+ ( "has" , false ) => match args {
97+ [ _path] => Self :: HasPath ,
98+ [ _path, value] => Self :: HasValue { value : value. clone ( ) } ,
99+ _ => panic ! ( "`//@ has` must have 2 or 3 arguments, but got {args:?}" ) ,
100+ } ,
101+ ( "has" , true ) => match args {
102+ [ _path] => Self :: HasNotPath ,
103+ [ _path, value] => Self :: HasNotValue { value : value. clone ( ) } ,
104+ _ => panic ! ( "`//@ !has` must have 2 or 3 arguments, but got {args:?}" ) ,
105+ } ,
106+
107+ ( _, false ) if KNOWN_DIRECTIVE_NAMES . contains ( & command_name) => {
108+ return None ;
109+ }
110+ _ => {
111+ panic ! ( "Invalid command `//@ {}{command_name}`" , if negated { "!" } else { "" } )
112+ }
113+ } ;
114+
115+ Some ( ( kind, & args[ 0 ] ) )
116+ }
117+ }
118+
119+ impl Command {
120+ /// Performs the actual work of ensuring a command passes.
121+ pub fn check ( & self , cache : & mut Cache ) -> Result < ( ) , String > {
122+ let matches = cache. select ( & self . path ) ;
123+ match & self . kind {
124+ CommandKind :: HasPath => {
125+ if matches. is_empty ( ) {
126+ return Err ( "matched to no values" . to_owned ( ) ) ;
127+ }
128+ }
129+ CommandKind :: HasNotPath => {
130+ if !matches. is_empty ( ) {
131+ return Err ( format ! ( "matched to {matches:?}, but wanted no matches" ) ) ;
132+ }
133+ }
134+ CommandKind :: HasValue { value } => {
135+ let want_value = string_to_value ( value, cache) ;
136+ if !matches. contains ( & want_value. as_ref ( ) ) {
137+ return Err ( format ! (
138+ "matched to {matches:?}, which didn't contain {want_value:?}"
139+ ) ) ;
140+ }
141+ }
142+ CommandKind :: HasNotValue { value } => {
143+ let wantnt_value = string_to_value ( value, cache) ;
144+ if matches. contains ( & wantnt_value. as_ref ( ) ) {
145+ return Err ( format ! (
146+ "matched to {matches:?}, which contains unwanted {wantnt_value:?}"
147+ ) ) ;
148+ } else if matches. is_empty ( ) {
149+ return Err ( format ! (
150+ "got no matches, but expected some matched (not containing {wantnt_value:?}"
151+ ) ) ;
152+ }
153+ }
154+
155+ CommandKind :: Is { value } => {
156+ let want_value = string_to_value ( value, cache) ;
157+ let matched = get_one ( & matches) ?;
158+ if matched != want_value. as_ref ( ) {
159+ return Err ( format ! ( "matched to {matched:?} but want {want_value:?}" ) ) ;
160+ }
161+ }
162+ CommandKind :: IsNot { value } => {
163+ let wantnt_value = string_to_value ( value, cache) ;
164+ let matched = get_one ( & matches) ?;
165+ if matched == wantnt_value. as_ref ( ) {
166+ return Err ( format ! ( "got value {wantnt_value:?}, but want anything else" ) ) ;
167+ }
168+ }
169+
170+ CommandKind :: IsMany { values } => {
171+ // Serde json doesn't implement Ord or Hash for Value, so we must
172+ // use a Vec here. While in theory that makes setwize equality
173+ // O(n^2), in practice n will never be large enough to matter.
174+ let expected_values =
175+ values. iter ( ) . map ( |v| string_to_value ( v, cache) ) . collect :: < Vec < _ > > ( ) ;
176+ if expected_values. len ( ) != matches. len ( ) {
177+ return Err ( format ! (
178+ "Expected {} values, but matched to {} values ({:?})" ,
179+ expected_values. len( ) ,
180+ matches. len( ) ,
181+ matches
182+ ) ) ;
183+ } ;
184+ for got_value in matches {
185+ if !expected_values. iter ( ) . any ( |exp| & * * exp == got_value) {
186+ return Err ( format ! ( "has match {got_value:?}, which was not expected" , ) ) ;
187+ }
188+ }
189+ }
190+ CommandKind :: CountIs { expected } => {
191+ if * expected != matches. len ( ) {
192+ return Err ( format ! (
193+ "matched to `{matches:?}` with length {}, but expected length {expected}" ,
194+ matches. len( ) ,
195+ ) ) ;
196+ }
197+ }
198+ CommandKind :: Set { variable } => {
199+ let value = get_one ( & matches) ?;
200+ let r = cache. variables . insert ( variable. to_owned ( ) , value. clone ( ) ) ;
201+ assert ! ( r. is_none( ) , "name collision: {variable:?} is duplicated" ) ;
202+ }
203+ }
204+
205+ Ok ( ( ) )
206+ }
207+ }
208+
209+ fn get_one < ' a > ( matches : & [ & ' a Value ] ) -> Result < & ' a Value , String > {
210+ match matches {
211+ [ ] => Err ( "matched to no values" . to_owned ( ) ) ,
212+ [ matched] => Ok ( matched) ,
213+ _ => Err ( format ! ( "matched to multiple values {matches:?}, but want exactly 1" ) ) ,
214+ }
215+ }
216+
217+ // FIXME: This setup is temporary until we figure out how to improve this situation.
218+ // See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>.
219+ include ! ( concat!( env!( "CARGO_MANIFEST_DIR" ) , "/../compiletest/src/directive-list.rs" ) ) ;
220+
221+ fn string_to_value < ' a > ( s : & str , cache : & ' a Cache ) -> Cow < ' a , Value > {
222+ if s. starts_with ( "$" ) {
223+ Cow :: Borrowed ( & cache. variables . get ( & s[ 1 ..] ) . unwrap_or_else ( || {
224+ // FIXME(adotinthevoid): Show line number
225+ panic ! ( "No variable: `{}`. Current state: `{:?}`" , & s[ 1 ..] , cache. variables)
226+ } ) )
227+ } else {
228+ Cow :: Owned ( serde_json:: from_str ( s) . expect ( & format ! ( "Cannot convert `{}` to json" , s) ) )
229+ }
230+ }
0 commit comments