diff --git a/examples/counter.basic b/examples/counter.basic new file mode 100644 index 0000000..92834db --- /dev/null +++ b/examples/counter.basic @@ -0,0 +1,3 @@ +10 LET X = 0 +20 X = X + 1 +30 GOTO 20 diff --git a/src/compile/mod.rs b/src/compile/mod.rs new file mode 100644 index 0000000..0f61090 --- /dev/null +++ b/src/compile/mod.rs @@ -0,0 +1,292 @@ +use regex::Regex; + +use crate::parse::{BasicAstBlock, BasicAstExpression, Operator}; + +#[derive(Debug, Clone)] +pub enum Operand { + Variable(String), + String(String), + Integer(i64), + Float(f64), +} + +impl std::fmt::Display for Operand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Operand::Variable(name) => write!(f, "{}", name), + Operand::String(string) => { + todo!(); + // write!(f, "\"{}\"", string), + } + Operand::Integer(int) => write!(f, "{}", int), + Operand::Float(float) => write!(f, "{}", float), + } + } +} + +#[derive(Debug, Clone)] +pub enum MindustryOperation { + JumpLabel(String), + Jump(String), + JumpIf(String, Operator, Operand, Operand), + Operator(String, Operator, Operand, Operand), + Set(String, Operand), +} + +#[derive(Debug, Clone)] +pub struct MindustryProgram(Vec); + +impl MindustryProgram { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn push(&mut self, operation: MindustryOperation) { + self.0.push(operation); + } + + pub fn append(&mut self, other: &mut Self) { + self.0.append(&mut other.0); + } +} + +impl From> for MindustryProgram { + fn from(value: Vec) -> Self { + Self(value) + } +} + +pub struct Namer { + var_index: usize, + label_index: usize, + prefix: String, +} + +impl Default for Namer { + fn default() -> Self { + Self { + var_index: 0, + label_index: 0, + prefix: String::from("main"), + } + } +} + +impl Namer { + fn temporary(&mut self) -> String { + let res = format!("{}__tmp_{}", self.prefix, self.var_index); + self.var_index += 1; + res + } + + fn label(&mut self, suffix: &str) -> String { + let res = format!("{}__label_{}_{}", self.prefix, self.label_index, suffix); + self.label_index += 1; + res + } +} + +fn translate_expression( + expression: &BasicAstExpression, + namer: &mut Namer, + target_name: String, +) -> MindustryProgram { + match expression { + BasicAstExpression::Integer(int) => { + vec![MindustryOperation::Set(target_name, Operand::Integer(*int))].into() + } + BasicAstExpression::Float(float) => { + vec![MindustryOperation::Set(target_name, Operand::Float(*float))].into() + } + BasicAstExpression::Variable(name) => vec![MindustryOperation::Set( + target_name, + Operand::Variable(name.clone()), + )] + .into(), + BasicAstExpression::Binary(op, left, right) => { + let left_name = namer.temporary(); + let right_name = namer.temporary(); + + let mut res = translate_expression(left.as_ref(), namer, left_name.clone()); + let mut right = translate_expression(right.as_ref(), namer, right_name.clone()); + + res.append(&mut right); + res.push(MindustryOperation::Operator( + target_name, + *op, + Operand::Variable(left_name), + Operand::Variable(right_name), + )); + + res + } + } +} + +pub fn translate_ast(basic_ast: &BasicAstBlock, namer: &mut Namer) -> MindustryProgram { + use crate::parse::BasicAstInstruction as Instr; + let mut res = MindustryProgram::new(); + + for instruction in basic_ast.instructions.iter() { + match instruction { + Instr::JumpLabel(label) => { + res.push(MindustryOperation::JumpLabel(label.clone())); + } + Instr::Jump(to) => { + res.push(MindustryOperation::Jump(to.clone())); + } + Instr::Assign(name, expression) => { + let mut instructions = translate_expression(expression, namer, name.clone()); + res.append(&mut instructions); + } + Instr::IfThenElse(condition, true_branch, false_branch) => { + let condition_name = namer.temporary(); + res.append(&mut translate_expression( + condition, + namer, + condition_name.clone(), + )); + + if false_branch.instructions.len() > 0 { + let else_label = namer.label("else"); + let end_label = namer.label("endif"); + res.push(MindustryOperation::JumpIf( + else_label.clone(), + Operator::Neq, + Operand::Variable(condition_name), + Operand::Variable(String::from("true")), + )); + + res.append(&mut translate_ast(true_branch, namer)); + + res.push(MindustryOperation::Jump(end_label.clone())); + res.push(MindustryOperation::JumpLabel(else_label)); + + res.append(&mut translate_ast(false_branch, namer)); + + res.push(MindustryOperation::JumpLabel(end_label)); + } else { + let end_label = namer.label("endif"); + res.push(MindustryOperation::JumpIf( + end_label.clone(), + Operator::Neq, + Operand::Variable(condition_name), + Operand::Variable(String::from("true")), + )); + + res.append(&mut translate_ast(true_branch, namer)); + + res.push(MindustryOperation::JumpLabel(end_label)); + } + } + } + } + + res +} + +impl std::fmt::Display for MindustryProgram { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let numeric = Regex::new(r"^[0-9]+$").unwrap(); + let numeric_jumps = + self.0 + .iter() + .filter_map(|op| match op { + MindustryOperation::Jump(target) + | MindustryOperation::JumpIf(target, _, _, _) => Some(target), + _ => None, + }) + .filter(|target| numeric.is_match(target)) + .cloned() + .collect::>(); + + for instruction in self.0.iter() { + match instruction { + MindustryOperation::JumpLabel(label) => { + if numeric.is_match(label) { + if numeric_jumps.contains(label) { + writeln!(f, "line__{}:", label)?; + } + } else { + writeln!(f, "{}:", label)?; + } + } + MindustryOperation::Jump(label) => { + if numeric.is_match(label) { + writeln!(f, "jump line__{} always 0 0", label)?; + } else { + writeln!(f, "jump {} always 0 0", label)?; + } + } + MindustryOperation::JumpIf(label, operator, lhs, rhs) => { + if numeric.is_match(label) { + writeln!( + f, + "jump line__{} {} {} {}", + label, + format_condition(*operator), + lhs, + rhs + )?; + } else { + writeln!( + f, + "jump {} {} {} {}", + label, + format_condition(*operator), + lhs, + rhs + )?; + } + } + MindustryOperation::Operator(name, operator, lhs, rhs) => { + writeln!( + f, + "op {} {} {} {}", + format_operator(*operator), + name, + lhs, + rhs + )?; + } + MindustryOperation::Set(name, value) => { + writeln!(f, "set {} {}", name, value)?; + } + } + } + + Ok(()) + } +} + +fn format_condition(operator: Operator) -> &'static str { + match operator { + Operator::Eq => "equal", + Operator::Neq => "notEqual", + Operator::Lt => "lessThan", + Operator::Lte => "lessThanEqual", + Operator::Gt => "greaterThan", + Operator::Gte => "greaterThanEqual", + x => { + panic!("Operator {:?} is not a condition!", x); + } + } +} + +fn format_operator(operator: Operator) -> &'static str { + match operator { + Operator::Eq => "equal", + Operator::Neq => "notEqual", + Operator::Lt => "lessThan", + Operator::Lte => "lessThanEqual", + Operator::Gt => "greaterThan", + Operator::Gte => "greaterThanEqual", + Operator::Add => "add", + Operator::Sub => "sub", + Operator::Mul => "mul", + Operator::Div => "div", + Operator::Mod => "mod", + Operator::RShift => "shr", + Operator::LShift => "shl", + } +} diff --git a/src/lib.rs b/src/lib.rs index 1b52c78..347d1fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +pub mod compile; pub mod cursor; pub mod parse; diff --git a/src/main.rs b/src/main.rs index e7a11a9..56bdc91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,15 @@ +use basic_to_mindustry::{ + compile::{translate_ast, Namer}, + parse::{build_ast, tokenize}, +}; + fn main() { - println!("Hello, world!"); + let path = std::env::args().nth(1).expect("Expected 1 argument"); + let source = std::fs::read_to_string(path).expect("Couldn't read input file"); + + let tokens = tokenize(&source).unwrap(); + let parsed = build_ast(&tokens).unwrap(); + let transformed = translate_ast(&parsed, &mut Namer::default()); + + println!("{}", transformed); } diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 1db93dc..c4deae7 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -28,27 +28,13 @@ impl_op_basic_ast_expression!(std::ops::Mul, mul, Operator::Mul); impl_op_basic_ast_expression!(std::ops::Div, div, Operator::Div); #[derive(Clone, Debug, PartialEq)] -pub enum BasicAstOperation { +pub enum BasicAstInstruction { + JumpLabel(String), Assign(String, BasicAstExpression), Jump(String), IfThenElse(BasicAstExpression, BasicAstBlock, BasicAstBlock), } -#[derive(Clone, Debug, PartialEq)] -pub struct BasicAstInstruction { - pub label: Option, - pub operation: BasicAstOperation, -} - -impl From for BasicAstInstruction { - fn from(operation: BasicAstOperation) -> Self { - Self { - label: None, - operation, - } - } -} - #[derive(Clone, Debug, PartialEq, Default)] pub struct BasicAstBlock { pub instructions: Vec, @@ -154,29 +140,27 @@ pub(crate) fn parse_expression( pub fn build_ast(tokens: &[BasicToken]) -> Result { let mut tokens = Cursor::from(tokens); let mut instructions = Vec::new(); - let mut current_label: Option = None; while tokens.len() > 0 { match tokens.peek(3) { [BasicToken::NewLine, BasicToken::Integer(label), ..] => { tokens.take(2); - current_label = Some(label.to_string()); + instructions.push(BasicAstInstruction::JumpLabel(label.to_string())); } [BasicToken::NewLine, BasicToken::Name(label), BasicToken::LabelEnd, ..] => { tokens.take(3); - current_label = Some(label.clone()); + instructions.push(BasicAstInstruction::JumpLabel(label.clone())); } [BasicToken::NewLine, ..] => { tokens.take(1); - current_label = None; } [BasicToken::Name(variable_name), BasicToken::Assign, ..] => { tokens.take(2); let expression = parse_expression(&mut tokens)?; - instructions.push(BasicAstInstruction { - label: current_label.take(), - operation: BasicAstOperation::Assign(variable_name.clone(), expression), - }); + instructions.push(BasicAstInstruction::Assign( + variable_name.clone(), + expression, + )); } [BasicToken::If, ..] => { tokens.take(1); @@ -188,41 +172,29 @@ pub fn build_ast(tokens: &[BasicToken]) -> Result { 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 { - label: current_label.take(), - operation: BasicAstOperation::IfThenElse( - condition, - true_branch, - false_branch, - ), - }); + instructions.push(BasicAstInstruction::IfThenElse( + condition, + true_branch, + false_branch, + )); } else { let true_branch = build_ast(&tokens[(then_index + 1)..end_index])?; - instructions.push(BasicAstInstruction { - label: current_label.take(), - operation: BasicAstOperation::IfThenElse( - condition, - true_branch, - BasicAstBlock::default(), - ), - }); + instructions.push(BasicAstInstruction::IfThenElse( + condition, + true_branch, + BasicAstBlock::default(), + )); } tokens.take(end_index + 1); } [BasicToken::Goto, BasicToken::Integer(label), ..] => { tokens.take(2); - instructions.push(BasicAstInstruction { - label: current_label.take(), - operation: BasicAstOperation::Jump(label.to_string()), - }); + instructions.push(BasicAstInstruction::Jump(label.to_string())); } [BasicToken::Goto, BasicToken::Name(label), ..] => { tokens.take(2); - instructions.push(BasicAstInstruction { - label: current_label.take(), - operation: BasicAstOperation::Jump(label.clone()), - }); + instructions.push(BasicAstInstruction::Jump(label.clone())); } _ => { if cfg!(test) { diff --git a/src/parse/test.rs b/src/parse/test.rs index b728bd7..af8328f 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -256,36 +256,33 @@ fn test_build_ast(raw: &str) -> BasicAstBlock { fn test_ast_basics() { assert_eq!( test_build_ast("LET X = 2 * 5\n"), - BasicAstBlock::new([BasicAstOperation::Assign( + BasicAstBlock::new([BasicAstInstruction::Assign( String::from("X"), BasicAstExpression::Integer(2) * BasicAstExpression::Integer(5) - ) - .into()]) + )]) ); assert_eq!( test_build_ast("IF X < 0 THEN\nX = 0-X\nEND IF"), - BasicAstBlock::new([BasicAstOperation::IfThenElse( + BasicAstBlock::new([BasicAstInstruction::IfThenElse( BasicAstExpression::Binary( Operator::Lt, Box::new(BasicAstExpression::Variable(String::from("X"))), Box::new(BasicAstExpression::Integer(0)) ), - BasicAstBlock::new([BasicAstOperation::Assign( + BasicAstBlock::new([BasicAstInstruction::Assign( String::from("X"), BasicAstExpression::Integer(0) - BasicAstExpression::Variable(String::from("X")) - ) - .into()]), + )]), BasicAstBlock::default() - ) - .into(),]) + ),]) ); assert_eq!( test_build_ast("GOTO 10\nGOTO hello"), BasicAstBlock::new([ - BasicAstOperation::Jump(String::from("10")).into(), - BasicAstOperation::Jump(String::from("hello")).into(), + BasicAstInstruction::Jump(String::from("10")), + BasicAstInstruction::Jump(String::from("hello")), ]) ); } @@ -295,42 +292,26 @@ fn test_ast_labels() { assert_eq!( test_build_ast("10 LET X = 0\n20 LET Y = 2 * X\n"), BasicAstBlock::new([ - BasicAstInstruction { - label: Some(String::from("10")), - operation: BasicAstOperation::Assign( - String::from("X"), - BasicAstExpression::Integer(0) - ) - }, - BasicAstInstruction { - label: Some(String::from("20")), - operation: BasicAstOperation::Assign( - String::from("Y"), - BasicAstExpression::Integer(2) - * BasicAstExpression::Variable(String::from("X")) - ) - }, + BasicAstInstruction::JumpLabel(String::from("10")), + BasicAstInstruction::Assign(String::from("X"), BasicAstExpression::Integer(0)), + BasicAstInstruction::JumpLabel(String::from("20")), + BasicAstInstruction::Assign( + String::from("Y"), + BasicAstExpression::Integer(2) * BasicAstExpression::Variable(String::from("X")) + ), ]) ); assert_eq!( test_build_ast("start: LET X = 0\nmultiply: LET Y = 2 * X\n"), BasicAstBlock::new([ - BasicAstInstruction { - label: Some(String::from("start")), - operation: BasicAstOperation::Assign( - String::from("X"), - BasicAstExpression::Integer(0) - ) - }, - BasicAstInstruction { - label: Some(String::from("multiply")), - operation: BasicAstOperation::Assign( - String::from("Y"), - BasicAstExpression::Integer(2) - * BasicAstExpression::Variable(String::from("X")) - ) - }, + BasicAstInstruction::JumpLabel(String::from("start")), + BasicAstInstruction::Assign(String::from("X"), BasicAstExpression::Integer(0)), + BasicAstInstruction::JumpLabel(String::from("multiply")), + BasicAstInstruction::Assign( + String::from("Y"), + BasicAstExpression::Integer(2) * BasicAstExpression::Variable(String::from("X")) + ), ]) ); }