Support nested ifs, add a generic mindustry operation

main
Shad Amethyst 9 months ago
parent 1cb25f2d4f
commit eb0a48a481

@ -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

@ -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<Operand>),
}
#[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::<Vec<_>>();
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)?;
}
}
}
}

@ -6,6 +6,7 @@ pub enum BasicAstExpression {
Integer(i64),
Float(f64),
Variable(String),
String(String),
Binary(Operator, Box<BasicAstExpression>, Box<BasicAstExpression>),
}
@ -33,6 +34,8 @@ pub enum BasicAstInstruction {
Assign(String, BasicAstExpression),
Jump(String),
IfThenElse(BasicAstExpression, BasicAstBlock, BasicAstBlock),
Print(Vec<(BasicAstExpression, bool)>),
CallBuiltin(String, Vec<BasicAstExpression>),
}
#[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<BasicAstBlock, ParseError> {
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<BasicAstInstruction>, 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<BasicAstBlock, ParseError> {
[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<BasicAstBlock, ParseError> {
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<BasicAstBlock, ParseError> {
}
}
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 })
}

@ -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<Vec<BasicToken>, 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<Vec<BasicToken>, 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<Vec<BasicToken>, 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

Loading…
Cancel
Save