motogarage_parser/
lib.rs

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