motogarage_parser/
lib.rs

1use pest::Parser; 
2use pest_derive::Parser;
3use thiserror::Error; 
4
5// --- 1. PARSER SETUP ---
6// This is the main link to pest.
7// It tells pest_derive to generate a parser struct named MotogarageParser...
8#[derive(Parser)]
9#[grammar = "src/grammar.pest"] // ...and to use this grammar file to build it.
10pub struct MotogarageParser;
11
12// --- 2. ERROR HANDLING ---
13// Defines our library's custom error types.
14// 'thiserror' makes it easy to create good error messages.
15#[derive(Error, Debug)]
16pub enum MotoError {
17    // This variant will automatically wrap any parsing errors from pest.
18  #[error("Parsing error: {0}")]
19  ParseError(#[from] pest::error::Error<Rule>),
20
21    // A custom error for our own logic (e.g., if interpretation fails).
22  #[error("Interp error: {0}")]
23  InterpreterError(String),
24}
25
26// --- 3. ABSTRACT SYNTAX TREE (AST) ---
27// These structs and enums represent our language's *structure*.
28// The parser's job is to turn text into these Rust types.
29// 
30
31// 'Command' is the top-level instruction. A file is a Vec<Command>.
32#[derive(Debug, Clone, PartialEq)]
33pub enum Command {
34  Definition(Motorcycle), // Represents a 'DEFINE' command
35  Get(Query), // Represents a 'GET' command
36  Count(Query), // Represents a 'COUNT' command
37}
38
39// Represents the data for a single motorcycle.
40#[derive(Debug, Clone, Default, PartialEq)]
41pub struct Motorcycle {
42  pub name: String,
43  pub year: Option<u32>, // 'Option' is used because fields are optional
44  pub engine_cc: Option<u32>,
45  pub bike_type: Option<String>,
46}
47
48// Represents a 'GET' or 'COUNT' query.
49// It holds an optional condition. If 'None', it means "all bikes".
50#[derive(Debug, Clone, PartialEq)]
51pub struct Query {
52  pub condition: Option<Condition>,
53}
54
55// Represents a 'WHERE' clause, like "year > 2020".
56#[derive(Debug, Clone, PartialEq)]
57pub struct Condition {
58  pub field: String, // "year"
59  pub operator: String, // ">"
60  pub value: Value, // Number(2020)
61}
62
63// Represents the different types of values our language supports.
64#[derive(Debug, Clone, PartialEq)]
65pub enum Value {
66  Number(u32), // e.g., 2020, 600
67  StringType(String), // e.g., sport, cruiser (unquoted identifiers)
68  StringLiteral(String), // e.g., "Honda CBR" (quoted strings)
69}
70
71// Helper methods to easily extract Rust values from our AST 'Value' enum.
72impl Value {
73  // Returns the number, or 0 if it's not a number.
74  pub fn value_as_number(&self) -> u32 {
75    match self {
76      Value::Number(n) => *n,
77      _ => 0,
78    }
79  }
80  // Returns the string value, or an empty string.
81  pub fn value_as_string(&self) -> String {
82    match self {
83      Value::StringType(s) => s.clone(),
84      Value::StringLiteral(s) => s.clone(),
85      _ => String::new(),
86    }
87  }
88}
89
90// --- 4. INTERPRETER ---
91// The 'Garage' struct holds the state of our program (the list of bikes).
92// It executes the AST (the Vec<Command>).
93#[derive(Debug, Default)]
94pub struct Garage {
95  bikes: Vec<Motorcycle>, // Our in-memory "database"
96}
97
98impl Garage {
99  pub fn new() -> Self {
100    Self::default()
101  }
102
103    // The main execution loop. It takes the AST and runs each command.
104  pub fn execute(&mut self, program: Vec<Command>) -> Result<Vec<String>, MotoError> {
105    let mut results = Vec::new(); // Collects output from GET/COUNT
106
107    for command in program {
108      match command {
109                // If the command is 'Definition', add the bike to our state.
110        Command::Definition(bike) => {
111          self.bikes.push(bike);
112        }
113                // If 'Get', run the query and add the list of names to results.
114        Command::Get(query) => {
115          results.extend(self.run_query_get(query));
116        }
117                // If 'Count', run the query, count the items, and add the count as a string.
118        Command::Count(query) => {
119          let count = self.filter_bikes(&query).count();
120          results.push(format!("Bikes found: {}", count));
121        }
122      }
123    }
124    Ok(results) // Return all collected results.
125  }
126
127    // A reusable helper function to filter bikes based on a query.
128    // It returns an iterator for efficiency (no new Vec is created here).
129  fn filter_bikes<'a>(&'a self, query: &'a Query) -> impl Iterator<Item = &'a Motorcycle> {
130    self.bikes
131      .iter()
132            // .map_or(true, ...) means: if the condition is 'None', return 'true' (match all bikes).
133            // Otherwise, call the 'bike.matches(c)' function.
134      .filter(move |bike| query.condition.as_ref().map_or(true, |c| bike.matches(c)))
135  }
136
137    // The logic for 'GET'. It uses the filter helper and then collects the names.
138  fn run_query_get(&self, query: Query) -> Vec<String> {
139    self.filter_bikes(&query)
140      .map(|bike| bike.name.clone()) // Get only the names
141      .collect() // Collect into a new Vec<String>
142  }
143}
144
145// Logic for checking if a single bike matches a 'WHERE' condition.
146impl Motorcycle {
147  fn matches(&self, condition: &Condition) -> bool {
148    match condition.field.as_str() { // Check which field we are filtering on
149      "type" => self
150        .bike_type
151        .as_ref() // Get an Option<&String>
152                    // Check if the bike's type matches the value's string.
153        .map_or(false, |t| *t == condition.value.value_as_string()),
154      "year" => self.year.map_or(false, |y| { // 'y' is the bike's year
155        compare(y, &condition.operator, condition.value.value_as_number())
156      }),
157      "engine" => self.engine_cc.map_or(false, |e| { // 'e' is the bike's engine
158        compare(e, &condition.operator, condition.value.value_as_number())
159      }),
160      _ => false, // Unknown field, so it's not a match.
161    }
162  }
163}
164// A simple comparison helper for numbers.
165fn compare(a: u32, op: &str, b: u32) -> bool {
166  match op {
167    "=" => a == b,
168    ">" => a > b,
169    "<" => a < b,
170    _ => false, // Invalid operator
171  }
172}
173impl Condition {} // This is empty, which is fine.
174
175// --- 5. PARSING LOGIC (Text -> AST) ---
176// These functions transform the 'Pairs' from pest into our AST structs.
177
178// The main entry point for parsing.
179pub fn parse_moto_file(input: &str) -> Result<Vec<Command>, MotoError> {
180    // 1. Call pest to parse the input string using the 'file' rule.
181  let pairs = MotogarageParser::parse(Rule::file, input)?; // '?' handles errors
182  let mut ast = Vec::new();
183
184    // 2. Iterate over the pairs inside the 'file' rule.
185    // We use .next().unwrap().into_inner() to step inside the 'file' pair.
186  for pair in pairs.into_iter().next().unwrap().into_inner() {
187    match pair.as_rule() {
188      Rule::command => ast.push(parse_command(pair)), // Found a command, parse it.
189      Rule::EOI => (), // End Of Input, we are done.
190      _ => unreachable!(), // Should not happen if grammar is correct.
191    }
192  }
193  Ok(ast) // Return the completed Abstract Syntax Tree (AST).
194}
195
196// This function routes a 'command' pair to the correct specific parser.
197fn parse_command(pair: pest::iterators::Pair<Rule>) -> Command {
198    // A 'command' pair contains either a 'definition', 'query_get', or 'query_count'.
199  let inner = pair.into_inner().next().unwrap();
200  match inner.as_rule() {
201    Rule::definition => Command::Definition(parse_definition(inner)),
202    Rule::query_get => Command::Get(parse_query(inner)),
203    Rule::query_count => Command::Count(parse_query(inner)),
204    _ => unreachable!(),
205  }
206}
207
208// Parses a 'definition' pair into a 'Motorcycle' struct.
209fn parse_definition(pair: pest::iterators::Pair<Rule>) -> Motorcycle {
210  let mut inner_pairs = pair.into_inner();
211    // The first inner pair is always the 'string_literal' (the name).
212  let name = parse_string_literal(inner_pairs.next().unwrap());
213
214  let mut bike = Motorcycle {
215    name,
216    ..Default::default() // Fill the rest with None/Default
217  };
218
219    // The second inner pair is 'properties'. We loop over them.
220  let properties_pairs = inner_pairs.next().unwrap().into_inner();
221  for prop_pair in properties_pairs { // Each 'prop_pair' is a 'property' rule
222    let mut prop_inner = prop_pair.into_inner();
223    let field_name = prop_inner.next().unwrap().as_str(); // e.g., "year"
224    let value_pair = prop_inner.next().unwrap(); // The 'value' pair
225
226        // Match on the field name and update the bike struct.
227    match field_name {
228      "year" => bike.year = Some(parse_value(value_pair).value_as_number()),
229      "engine" => bike.engine_cc = Some(parse_value(value_pair).value_as_number()),
230      "type" => bike.bike_type = Some(parse_value(value_pair).value_as_string()),
231      _ => {} // Ignore unknown properties
232    }
233  }
234  bike
235}
236
237// Parses a 'query_get' or 'query_count' pair into a 'Query' struct.
238// Note: This logic is shared by both GET and COUNT.
239fn parse_query(pair: pest::iterators::Pair<Rule>) -> Query {
240    // A query pair contains an optional 'where_clause'.
241  let where_clause_pair = pair.into_inner().next();
242
243    // If the 'where_clause' exists, get the 'condition' from inside it.
244  let condition_pair =
245    where_clause_pair.map(|where_pair| where_pair.into_inner().next().unwrap());
246
247    // If the 'condition' exists, parse it.
248  let condition = condition_pair.map(parse_condition);
249
250  Query { condition } // Create the Query struct
251}
252
253// Parses a 'condition' pair into a 'Condition' struct.
254fn parse_condition(pair: pest::iterators::Pair<Rule>) -> Condition {
255    // A 'condition' pair contains 'ident', 'operator', 'value'.
256  let mut inner = pair.into_inner();
257  let field = inner.next().unwrap().as_str().to_string();
258  let operator = inner.next().unwrap().as_str().to_string();
259  let value = parse_value(inner.next().unwrap());
260  Condition {
261    field,
262    operator,
263    value,
264  }
265}
266
267// Parses a 'value' pair into our 'Value' enum.
268fn parse_value(pair: pest::iterators::Pair<Rule>) -> Value {
269    // A 'value' pair contains one of its inner rules.
270  let inner = pair.into_inner().next().unwrap();
271  match inner.as_rule() {
272    Rule::number => Value::Number(inner.as_str().parse().unwrap_or(0)),
273    Rule::number_with_unit => {
274            // We must strip "cc" before parsing to a number.
275      Value::Number(inner.as_str().replace("cc", "").parse().unwrap_or(0))
276    }
277    Rule::ident => Value::StringType(inner.as_str().to_string()),
278    Rule::string_literal => Value::StringLiteral(parse_string_literal(inner)),
279    _ => unreachable!(),
280  }
281}
282
283// Helper to clean up quoted strings.
284fn parse_string_literal(pair: pest::iterators::Pair<Rule>) -> String {
285  pair.as_str().trim_matches('"').to_string() // Removes the "" from the string.
286}