use bevy::prelude::*; #[derive(Component)] pub struct TerminalSystem; #[derive(Clone, Debug)] enum Token { Identifier(String), Integer(i32), Period, Semicolon, LParen, RParen } impl std::fmt::Display for Token { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Token::Identifier(id) => write!(f, "Identifer({})", id), Token::Integer(int) => write!(f, "Integer({})", int), Token::LParen => write!(f, "("), Token::RParen => write!(f, ")"), Token::Period => write!(f, "."), Token::Semicolon => write!(f, ";") } } } #[derive(Debug)] pub enum CallArg { Integer(i32) } #[derive(Debug)] pub struct CallNode { target: Option, method: String, args: Vec } impl std::fmt::Display for CallNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}({})", match &self.target { Some(val) => { val.to_owned() + "." }, None => "".into() }, self.method, { let mut str_args: Vec = Vec::new(); for arg in &self.args { match arg { CallArg::Integer(int) => { str_args.push(int.to_string()); } } } str_args.join(", ") }) } } #[derive(Event)] pub struct TerminalEvent(Vec); impl TerminalEvent { pub fn new(nodes: Vec) -> Self { Self(nodes) } } #[derive(Component)] pub struct TerminalObject(String, TObjectType); impl TerminalObject { pub fn new(val: String, object_type: TObjectType) -> Self { Self(val.to_owned(), object_type) } } pub enum TObjectType { Door, Laser } pub fn plugin(app: &mut App) { app.add_observer(|event: On, terminal_objects: Query<&TerminalObject>| { for node in &event.0 { if node.method == "wait" && matches!(node.target, None) { // call wait } else { if matches!(node.target, None) { panic!("invalid target None"); } if let Some(obj) = terminal_objects.iter().find(|o| o.0 == *node.target.as_ref().unwrap()) { match obj.1 { TObjectType::Door => { if node.method == "open" { // call open println!("open door {}", obj.0); } else if node.method == "close" { // call close println!("close door {}", obj.0); } else { panic!("invalid method door.{}", node.method); } }, TObjectType::Laser => { if node.method == "on" { // call on println!("turn on laser {}", obj.0); } else if node.method == "off" { // call off println!("turn off laser {}", obj.0); } else { panic!("invalid method laser.{}", node.method); } } } } else { panic!("invalid target {}", node.target.as_ref().unwrap()); } } } }); } impl TerminalSystem { pub fn parse(&mut self, input: String) -> Vec { let tokens = self.lex(input); self.parse_tokens(tokens) } fn lex(&mut self, input: String) -> Vec { let mut tokens = Vec::new(); let mut buffer = String::new(); let mut in_id = false; let mut in_int = false; let mut iter = 0; while iter < input.len() { let chr = input.chars().skip(iter).take(1).collect::(); if in_id { if chr.chars().any(|c| matches!(c, 'a'..='z')) || "0123456789".contains(&chr) { buffer += &chr; iter += 1; continue; } else { tokens.push(Token::Identifier(buffer.clone())); in_id = false; continue; } } else if in_int { if "0123456789".contains(&chr) { buffer += &chr; iter += 1; continue; } else { tokens.push(Token::Integer(buffer.parse::().unwrap())); in_int = false; continue; } } else if chr == ";" { tokens.push(Token::Semicolon); iter += 1; continue; } else if chr == "." { tokens.push(Token::Period); iter += 1; continue; } else if chr == "(" { tokens.push(Token::LParen); iter += 1; continue; } else if chr == ")" { tokens.push(Token::RParen); iter += 1; continue; } else if chr == " " { iter += 1; continue; } else if chr.chars().any(|c| matches!(c, 'a'..='z') || matches!(c, 'A'..='Z')) { in_id = true; buffer = chr.clone(); iter += 1; continue; } else if "0123456789".contains(&chr) { if !in_int { in_int = true; buffer = chr.clone(); iter += 1; continue; } } else { panic!("Need to figure out how to throw errors"); } } tokens } fn parse_tokens(&mut self, tokens: Vec) -> Vec { let mut nodes = Vec::new(); let mut iter = 0; let consume = |tokens: &Vec, mut iter: &mut usize, expected_type: Option| -> Token { let token = { if *iter < tokens.len() { Some(&tokens[*iter]) } else { None } }; if let Some(exp_type) = expected_type { if let Some(Token::Identifier(_)) = token { if let Token::Identifier(_) = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else if let Some(Token::Integer(_)) = token { if let Token::Integer(_) = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else if let Some(Token::LParen) = token { if let Token::LParen = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else if let Some(Token::Period) = token { if let Token::Period = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else if let Some(Token::RParen) = token { if let Token::RParen = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else if let Some(Token::Semicolon) = token { if let Token::Semicolon = exp_type { (*iter) += 1; (*token.unwrap()).clone() } else { panic!("Expected type not found"); } } else { panic!("Not sure why this would happen"); } } else { (*iter) += 1; (*token.unwrap()).clone() } }; let parse_call = |nodes: &mut Vec, tokens: &Vec, mut iter: &mut usize| { let first_id = consume(tokens, iter, Some(Token::Identifier(String::new()))); let mut target: Option = None; let mut method = first_id.clone(); if matches!(tokens[*iter], Token::Period) { consume(tokens, iter, Some(Token::Period)); target = Some(first_id.clone()); method = consume(tokens, iter, Some(Token::Identifier(String::new()))) } consume(tokens, iter, Some(Token::LParen)); let mut args: Vec = Vec::new(); let mut current_token = { if *iter < tokens.len() { Some(&tokens[*iter]) } else { None } }; while matches!(current_token, Some(_)) && !matches!(current_token, Some(Token::RParen)) { let arg_token = consume(tokens, iter, Some(Token::Integer(0))); match arg_token { Token::Integer(val) => { args.push(CallArg::Integer(val)); }, _ => panic!("invalid") } current_token = { if *iter < tokens.len() { Some(&tokens[*iter]) } else { None } }; } consume(tokens, iter, Some(Token::RParen)); nodes.push(CallNode{ target: { match target { Some(Token::Identifier(id)) => { Some(id.to_owned()) }, Some(_) => panic!("invalid"), None => None } }, method: match method { Token::Identifier(id) => { id.to_owned() }, _ => panic!("invalid") }, args }); }; while iter < tokens.len() { parse_call(&mut nodes, &tokens, &mut iter); let current_token = { if iter < tokens.len() { Some(&tokens[iter]) } else { None } }; if matches!(current_token, Some(Token::Semicolon)) { consume(&tokens, &mut iter, Some(Token::Semicolon)); } } nodes } }