From eb0a48a481a62eb3807022d90152302e31a93c6f Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Tue, 26 Sep 2023 16:28:31 +0200 Subject: [PATCH] :sparkles: Support nested ifs, add a generic mindustry operation --- examples/fizzbuzz.basic | 21 ++++++ src/compile/mod.rs | 56 ++++++++++++++- src/parse/ast.rs | 151 +++++++++++++++++++++++++++++++++++----- src/parse/tokenize.rs | 15 ++-- 4 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 examples/fizzbuzz.basic diff --git a/examples/fizzbuzz.basic b/examples/fizzbuzz.basic new file mode 100644 index 0000000..059a31e --- /dev/null +++ b/examples/fizzbuzz.basic @@ -0,0 +1,21 @@ +x = 0 + +start: + x = x + 1 + IF x % 3 == 0 THEN + IF x % 5 == 0 THEN + PRINT "fizzbuzz" + ELSE + PRINT "fizz" + END IF + ELSE + IF x % 5 == 0 THEN + PRINT "buzz" + ELSE + PRINT x + END IF + END IF + + PRINT_FLUSH(message1) + +GOTO start diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 0f61090..1a13cea 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -15,8 +15,8 @@ impl std::fmt::Display for Operand { match self { Operand::Variable(name) => write!(f, "{}", name), Operand::String(string) => { - todo!(); - // write!(f, "\"{}\"", string), + let escaped = string.replace('\\', r#"\\"#).replace('"', r#"\""#); + write!(f, "\"{}\"", escaped) } Operand::Integer(int) => write!(f, "{}", int), Operand::Float(float) => write!(f, "{}", float), @@ -31,6 +31,7 @@ pub enum MindustryOperation { JumpIf(String, Operator, Operand, Operand), Operator(String, Operator, Operand, Operand), Set(String, Operand), + Generic(String, Vec), } #[derive(Debug, Clone)] @@ -103,6 +104,11 @@ fn translate_expression( Operand::Variable(name.clone()), )] .into(), + BasicAstExpression::String(string) => vec![MindustryOperation::Set( + target_name, + Operand::String(string.clone()), + )] + .into(), BasicAstExpression::Binary(op, left, right) => { let left_name = namer.temporary(); let right_name = namer.temporary(); @@ -179,6 +185,46 @@ pub fn translate_ast(basic_ast: &BasicAstBlock, namer: &mut Namer) -> MindustryP res.push(MindustryOperation::JumpLabel(end_label)); } } + Instr::Print(expressions) => { + for expression in expressions { + let tmp_name = namer.temporary(); + res.append(&mut translate_expression( + &expression.0, + namer, + tmp_name.clone(), + )); + res.push(MindustryOperation::Generic( + String::from("print"), + vec![Operand::Variable(tmp_name)], + )); + } + } + Instr::CallBuiltin(name, arguments) => { + let argument_names = (0..arguments.len()) + .map(|_| namer.temporary()) + .collect::>(); + for (i, argument) in arguments.iter().enumerate() { + res.append(&mut translate_expression( + argument, + namer, + argument_names[i].clone(), + )); + } + + match name.as_str() { + "print_flush" => { + if arguments.len() == 1 { + res.push(MindustryOperation::Generic( + String::from("printflush"), + vec![Operand::Variable(argument_names[0].clone())], + )); + } else { + panic!("Invalid amount of arguments: {}", arguments.len()); + } + } + _ => unimplemented!(), + } + } } } @@ -252,6 +298,12 @@ impl std::fmt::Display for MindustryProgram { MindustryOperation::Set(name, value) => { writeln!(f, "set {} {}", name, value)?; } + MindustryOperation::Generic(name, operands) => { + write!(f, "{}", name)?; + for operand in operands { + write!(f, " {}", operand)?; + } + } } } diff --git a/src/parse/ast.rs b/src/parse/ast.rs index c4deae7..0072590 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -6,6 +6,7 @@ pub enum BasicAstExpression { Integer(i64), Float(f64), Variable(String), + String(String), Binary(Operator, Box, Box), } @@ -33,6 +34,8 @@ pub enum BasicAstInstruction { Assign(String, BasicAstExpression), Jump(String), IfThenElse(BasicAstExpression, BasicAstBlock, BasicAstBlock), + Print(Vec<(BasicAstExpression, bool)>), + CallBuiltin(String, Vec), } #[derive(Clone, Debug, PartialEq, Default)] @@ -86,6 +89,10 @@ pub(crate) fn parse_expression( tokens.take(1); Ok(BasicAstExpression::Variable(name.clone())) } + [BasicToken::String(string), ..] => { + tokens.take(1); + Ok(BasicAstExpression::String(string.clone())) + } [BasicToken::OpenParen, ..] => { tokens.take(1); let res = parse_expression(tokens)?; @@ -138,10 +145,21 @@ pub(crate) fn parse_expression( } pub fn build_ast(tokens: &[BasicToken]) -> Result { + enum Context { + Main, + If(BasicAstExpression), + IfElse(BasicAstExpression, BasicAstBlock), + } + let mut tokens = Cursor::from(tokens); - let mut instructions = Vec::new(); + let mut context_stack: Vec<(Vec, Context)> = + vec![(Vec::new(), Context::Main)]; while tokens.len() > 0 { + let Some((ref mut instructions, _context)) = context_stack.last_mut() else { + unreachable!("Context stack got emptied!"); + }; + match tokens.peek(3) { [BasicToken::NewLine, BasicToken::Integer(label), ..] => { tokens.take(2); @@ -165,28 +183,59 @@ pub fn build_ast(tokens: &[BasicToken]) -> Result { [BasicToken::If, ..] => { tokens.take(1); let then_index = find_token_index(&tokens, BasicToken::Then)?; - let end_index = find_token_index(&tokens, BasicToken::EndIf)?; + // let end_index = find_token_index(&tokens, BasicToken::EndIf)?; let condition = parse_expression(&mut tokens.range(0..then_index))?; - if let Ok(else_index) = find_token_index(&tokens, BasicToken::Else) { - let true_branch = build_ast(&tokens[(then_index + 1)..else_index])?; - let false_branch = build_ast(&tokens[(else_index + 1)..end_index])?; - - instructions.push(BasicAstInstruction::IfThenElse( - condition, - true_branch, - false_branch, - )); - } else { - let true_branch = build_ast(&tokens[(then_index + 1)..end_index])?; - instructions.push(BasicAstInstruction::IfThenElse( - condition, - true_branch, - BasicAstBlock::default(), - )); + + tokens.take(then_index + 1); + + context_stack.push((Vec::new(), Context::If(condition))); + } + [BasicToken::Else, ..] => { + tokens.take(1); + + match context_stack.pop().unwrap() { + (instructions, Context::If(condition)) => { + context_stack.push(( + Vec::new(), + Context::IfElse(condition, BasicAstBlock { instructions }), + )); + } + (_instructions, _) => { + return Err(ParseError::UnexpectedToken(BasicToken::Else)); + } } + } + [BasicToken::EndIf, ..] => { + tokens.take(1); - tokens.take(end_index + 1); + match context_stack.pop().unwrap() { + (instructions, Context::If(condition)) => { + let Some((ref mut parent_instructions, _)) = context_stack.last_mut() else { + unreachable!("Context::If not wrapped in another context"); + }; + + parent_instructions.push(BasicAstInstruction::IfThenElse( + condition, + BasicAstBlock { instructions }, + BasicAstBlock::default(), + )); + } + (instructions, Context::IfElse(condition, true_branch)) => { + let Some((ref mut parent_instructions, _)) = context_stack.last_mut() else { + unreachable!("Context::IfElse not wrapped in another context"); + }; + + parent_instructions.push(BasicAstInstruction::IfThenElse( + condition, + true_branch, + BasicAstBlock { instructions }, + )); + } + _ => { + return Err(ParseError::UnexpectedToken(BasicToken::EndIf)); + } + } } [BasicToken::Goto, BasicToken::Integer(label), ..] => { tokens.take(2); @@ -196,6 +245,53 @@ pub fn build_ast(tokens: &[BasicToken]) -> Result { tokens.take(2); instructions.push(BasicAstInstruction::Jump(label.clone())); } + [BasicToken::Print, ..] => { + tokens.take(1); + + let mut expressions = Vec::new(); + + if let Some(BasicToken::NewLine) = tokens.get(0) { + instructions.push(BasicAstInstruction::Print(expressions)); + } else { + expressions.push((parse_expression(&mut tokens)?, false)); + + while let Some(BasicToken::Comma) = tokens.get(0) { + expressions.push((parse_expression(&mut tokens)?, false)); + } + + instructions.push(BasicAstInstruction::Print(expressions)); + } + + // TODO: expect newline + } + [BasicToken::Name(fn_name), BasicToken::OpenParen, ..] => { + tokens.take(2); + let mut arguments = Vec::new(); + + while tokens.get(0) != Some(&BasicToken::CloseParen) { + arguments.push(parse_expression(&mut tokens)?); + + match tokens.take(1) { + [BasicToken::Comma] => {} + [BasicToken::CloseParen] => break, + _ => return Err(ParseError::MissingToken(BasicToken::Comma)), + } + } + + // TODO: expect closeparen + + tokens.take(1); + + let lowercase_fn_name = fn_name.to_lowercase(); + if matches!(lowercase_fn_name.as_str(), "print_flush") { + instructions.push(BasicAstInstruction::CallBuiltin( + lowercase_fn_name, + arguments, + )); + } else { + unimplemented!(); + } + } _ => { if cfg!(test) { eprintln!("No match found in main ast loop!"); @@ -207,5 +303,22 @@ pub fn build_ast(tokens: &[BasicToken]) -> Result { } } + if context_stack.len() == 0 { + unreachable!("Empty context stack"); + } else if context_stack.len() > 1 { + match &context_stack.last().unwrap().1 { + Context::If(_) | Context::IfElse(_, _) => { + return Err(ParseError::MissingToken(BasicToken::EndIf)); + } + Context::Main => { + unreachable!("There cannot be another context below the main context"); + } + } + } + + let (instructions, Context::Main) = context_stack.into_iter().next().unwrap() else { + unreachable!("The lowermost context must be the main context"); + }; + Ok(BasicAstBlock { instructions }) } diff --git a/src/parse/tokenize.rs b/src/parse/tokenize.rs index ca1591f..07a5563 100644 --- a/src/parse/tokenize.rs +++ b/src/parse/tokenize.rs @@ -43,6 +43,8 @@ pub enum BasicToken { LabelEnd, OpenParen, CloseParen, + Comma, + Print, Integer(i64), Float(f64), Name(String), @@ -105,12 +107,13 @@ pub fn tokenize(raw: &str) -> Result, ParseError> { let mut res = Vec::new(); let match_let = Regex::new(r"(?i)^let").unwrap(); let match_jump = Regex::new(r"(?i)^go\s*to").unwrap(); - let match_word = Regex::new(r"(?i)^(?:if|then|else|end\s?if)").unwrap(); + let match_word = Regex::new(r"(?i)^(?:if|then|else|end\s?if|print)(?:\s|$)").unwrap(); let match_space = Regex::new(r"^\s+").unwrap(); let match_variable = Regex::new(r"^@?[a-zA-Z_][a-zA-Z_0-9]*").unwrap(); let match_float = Regex::new(r"^[0-9]*\.[0-9]+").unwrap(); let match_integer = Regex::new(r"^[0-9]+").unwrap(); let match_assign = Regex::new(r"^=").unwrap(); + let match_comma = Regex::new(r"^,").unwrap(); let match_operator = Regex::new(r"^(?:[+\-*/%]|[<>]=?|[!=]=|<<|>>)").unwrap(); let match_label_end = Regex::new(r"^:").unwrap(); let match_paren = Regex::new(r"^(?:\(|\))").unwrap(); @@ -130,17 +133,18 @@ pub fn tokenize(raw: &str) -> Result, ParseError> { match_let => (), match_comment => (), match_jump => (BasicToken::Goto), - match_word(word) => (match word.to_lowercase().as_str() { + match_word(word) => (match word.to_lowercase().as_str().trim() { "if" => BasicToken::If, "then" => BasicToken::Then, "else" => BasicToken::Else, "end if" | "endif" => BasicToken::EndIf, - _ => unreachable!(), + "print" => BasicToken::Print, + _ => unreachable!("{}", word), }), match_variable(name) => (BasicToken::Name(name.to_string())), match_float(float) => (BasicToken::Float(float.parse().unwrap())), match_integer(int) => (BasicToken::Integer(int.parse().unwrap())), - match_assign => (BasicToken::Assign), + match_comma => (BasicToken::Comma), match_operator(op) => (BasicToken::Operator(match op { "+" => Operator::Add, "-" => Operator::Sub, @@ -153,8 +157,11 @@ pub fn tokenize(raw: &str) -> Result, ParseError> { ">=" => Operator::Gte, "<<" => Operator::LShift, ">>" => Operator::RShift, + "==" => Operator::Eq, + "!=" => Operator::Neq, _ => unreachable!(), })), + match_assign => (BasicToken::Assign), match_label_end => (BasicToken::LabelEnd), match_paren(paren) => (if paren == "(" { BasicToken::OpenParen