main
Shad Amethyst 9 months ago
parent c5299c719f
commit 55d78ab5ce

@ -157,4 +157,51 @@ END IF
## Loops
<!-- TODO: finish implementing WHILE..WEND and DO..LOOP -->
MinBasic offers multiple ways to execute a block of code multiple times, on top of manually jumping to an earlier point in the code:
### `FOR` loops
`FOR` loops allow you to run a piece of code for a fixed amount of iterations, incrementing a variable when doing so.
The syntax is as follows:
```basic
REM Prints the numbers from 1 to 10, with 10 included
FOR x = 1 TO 10
PRINT x
NEXT x
```
The `FOR` keyword expects a variable name (here `x`), an initial value (here `1`), a maximal value (here `10`), and optionally an increment, which defaults to `1`.
To specify the increment, append `STEP n` to the `FOR` instruction: `FOR x = 1 TO 10 STEP 2`.
The loop body is then executed, until the `NEXT` statement is reached, telling the loop to jump to the beginning, increment the variable, compare it and possibly execute the loop body.
If the initial value is bigger than the maximal value, then the loop body will not be executed.
### `WHILE` loops
`WHILE` loops allow you to execute a piece of code any amount of time, until a condition turns false.
The syntax is as follows:
```basic
REM Divides x until it is an odd number
WHILE x % 2 == 0
x = x / 2
WEND
```
If the condition (here `x % 2 == 0`) yields false on the first iteration, then the loop body will not be executed.
`WEND` may be replaced with `END WHILE`, similar to VisualBasic.
### `DO WHILE` loops
Much like `WHILE` loops, `DO WHILE` loops execute a piece of code until a condition turns false.
The difference, however, is that the loop body will be executed at least once:
```basic
REM Will repeatedly set x to 3*x+1, until it becomes an even number
DO WHILE x % 2 == 1
x = 3*x + 1
LOOP
```

@ -0,0 +1,15 @@
X = 7
WHILE X != 1
WHILE X % 2 == 1
X = 3*X + 1
PRINT X, " "
END WHILE
DO
X = X / 2
PRINT X, " "
LOOP WHILE X % 2 == 0
WEND
PRINT_FLUSH(message1)

@ -2,6 +2,37 @@ use super::*;
use crate::cursor::Cursor;
use crate::prelude::*;
/// Pops the last context, matches on it, and pushes the result of the match branch to the parent instruction list.
/// Errors can be returned early, and branches need to be exhaustive.
macro_rules! pop_context {
(
$context_stack:expr, $instructions:ident,
{ $( $match:pat => $instr:block $(,)? )* }
) => {
match $context_stack.pop() {
Some(($instructions, context)) => {
match context {
$(
$match => {
let Some((ref mut parent_instructions, _)) = $context_stack.last_mut()
else {
unreachable!("{} not wrapped in another context", stringify!($match));
};
#[allow(unreachable_code)]
parent_instructions.push($instr);
},
)*
}
}
None => {
unreachable!("Empty context stack");
},
}
}
}
pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock, ParseError> {
enum Context {
Main,
@ -13,6 +44,9 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
BasicAstExpression,
BasicAstExpression,
),
While(BasicAstExpression),
Do,
DoWhile(BasicAstExpression),
}
let mut tokens = Cursor::from(tokens);
@ -65,35 +99,25 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
[BasicToken::EndIf, ..] => {
tokens.take(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(
pop_context!(context_stack, instructions, {
Context::If(condition) => {
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(
)
},
Context::IfElse(condition, true_branch) => {
BasicAstInstruction::IfThenElse(
condition,
true_branch,
BasicAstBlock { instructions },
));
)
}
_ => {
return Err(ParseError::UnexpectedToken(BasicToken::EndIf));
}
}
});
}
// == For loops ==
[BasicToken::For, BasicToken::Name(variable), BasicToken::Assign, ..] => {
@ -118,37 +142,96 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
context_stack.push((Vec::new(), Context::For(variable.clone(), start, end, step)));
}
[BasicToken::Next, BasicToken::Name(variable), ..] => match context_stack.pop() {
Some((instructions, Context::For(expected_variable, start, end, step))) => {
tokens.take(2);
[BasicToken::Next, BasicToken::Name(variable), ..] => {
tokens.take(2);
let Some((ref mut parent_instructions, _)) = context_stack.last_mut() else {
unreachable!("Context::For not wrapped in another context");
};
pop_context!(context_stack, instructions, {
Context::For(expected_variable, start, end, step) => {
if *variable != expected_variable {
return Err(ParseError::WrongForVariable(
expected_variable,
variable.clone(),
));
}
if *variable != expected_variable {
return Err(ParseError::WrongForVariable(
expected_variable,
variable.clone(),
));
BasicAstInstruction::For {
variable: expected_variable,
start,
end,
step,
instructions: BasicAstBlock::new(instructions),
}
}
_ => {
return Err(ParseError::UnexpectedToken(BasicToken::Next));
}
});
}
// == While loops ==
[BasicToken::While, ..] => {
tokens.take(1);
let condition = parse_expression(&mut tokens)?;
expect_next_token(&tokens, &BasicToken::NewLine)?;
parent_instructions.push(BasicAstInstruction::For {
variable: expected_variable,
start,
end,
step,
instructions: BasicAstBlock::new(instructions),
});
}
Some((_instructions, _context)) => {
eprintln!("NEXT outside of loop");
return Err(ParseError::UnexpectedToken(BasicToken::Next));
}
None => {
unreachable!("Empty context stack");
}
},
context_stack.push((Vec::new(), Context::While(condition)));
}
[BasicToken::Do, BasicToken::While, ..] => {
tokens.take(2);
let condition = parse_expression(&mut tokens)?;
expect_next_token(&tokens, &BasicToken::NewLine)?;
context_stack.push((Vec::new(), Context::DoWhile(condition)));
}
[BasicToken::Do, ..] => {
tokens.take(1);
context_stack.push((Vec::new(), Context::Do));
expect_next_token(&tokens, &BasicToken::NewLine)?;
}
[BasicToken::Wend, ..] => {
tokens.take(1);
pop_context!(context_stack, instructions, {
Context::While(condition) => {
BasicAstInstruction::While(condition, BasicAstBlock::new(instructions))
},
_ => {
return Err(ParseError::UnexpectedToken(BasicToken::Wend));
}
});
}
[BasicToken::Loop, BasicToken::While, ..] => {
tokens.take(2);
let condition = parse_expression(&mut tokens)?;
pop_context!(context_stack, instructions, {
Context::Do => {
BasicAstInstruction::DoWhile(condition, BasicAstBlock::new(instructions))
},
Context::DoWhile(_) => {
return Err(ParseError::UnexpectedToken(BasicToken::While));
},
_ => {
return Err(ParseError::UnexpectedToken(BasicToken::Loop));
}
});
}
[BasicToken::Loop, ..] => {
tokens.take(1);
pop_context!(context_stack, instructions, {
Context::DoWhile(condition) => {
BasicAstInstruction::DoWhile(condition, BasicAstBlock::new(instructions))
},
Context::Do => {
return Err(ParseError::MissingToken(BasicToken::While));
},
_ => {
return Err(ParseError::UnexpectedToken(BasicToken::Wend));
}
});
}
// == Goto ==
[BasicToken::Goto, BasicToken::Integer(label), ..] => {
@ -269,6 +352,12 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
Context::For(_, _, _, _) => {
return Err(ParseError::MissingToken(BasicToken::Next));
}
Context::While(_) => {
return Err(ParseError::MissingToken(BasicToken::Wend));
}
Context::Do | Context::DoWhile(_) => {
return Err(ParseError::MissingToken(BasicToken::Loop));
}
Context::Main => {
unreachable!("There cannot be another context below the main context");
}

@ -15,6 +15,10 @@ pub enum BasicToken {
To,
Step,
Next,
While,
Wend,
Do,
Loop,
LabelEnd,
OpenParen,
CloseParen,
@ -75,14 +79,14 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
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|print|for|to|step|next)(?:\s|$)").unwrap();
Regex::new(r"(?i)^(?:if|then|else|end\s?(?:if|while)|print|for|to|step|next|while|do|wend|loop)(?:\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_operator = Regex::new(r"^(?:[+\-*/%]|[<>]=?|[!=]=|<>|<<|>>)").unwrap();
let match_label_end = Regex::new(r"^:").unwrap();
let match_paren = Regex::new(r"^(?:\(|\))").unwrap();
// TODO: handle escapes
@ -111,6 +115,11 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
"to" => BasicToken::To,
"step" => BasicToken::Step,
"next" => BasicToken::Next,
"while" => BasicToken::While,
"do" => BasicToken::Do,
"wend" => BasicToken::Wend,
"end while" => BasicToken::Wend,
"loop" => BasicToken::Loop,
_ => unreachable!("{}", word),
}),
match_variable(name) => (BasicToken::Name(name.to_string())),
@ -130,7 +139,7 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
"<<" => Operator::LShift,
">>" => Operator::RShift,
"==" => Operator::Eq,
"!=" => Operator::Neq,
"<>" | "!=" => Operator::Neq,
_ => unreachable!(),
})),
match_assign => (BasicToken::Assign),

@ -25,6 +25,8 @@ pub enum BasicAstInstruction {
step: BasicAstExpression,
instructions: BasicAstBlock,
},
While(BasicAstExpression, BasicAstBlock),
DoWhile(BasicAstExpression, BasicAstBlock),
}
#[derive(Clone, Debug, PartialEq, Default)]

@ -184,6 +184,7 @@ pub fn translate_ast(
instructions,
} => {
let start_label = namer.label("start");
let end_label = namer.label("end");
let end_name = namer.temporary();
let step_name = namer.temporary();
@ -192,22 +193,75 @@ pub fn translate_ast(
res.append(&mut translate_expression(end, namer, end_name.clone()));
res.append(&mut translate_expression(step, namer, step_name.clone()));
// Body
// Condition
res.push(MindustryOperation::JumpLabel(start_label.clone()));
res.push(MindustryOperation::JumpIf(
end_label.clone(),
Operator::Gt,
Operand::Variable(variable.clone()),
Operand::Variable(end_name),
));
// Body
res.append(&mut translate_ast(instructions, namer, config));
// Loop condition: increment variable and jump
// Increment variable and jump
res.push(MindustryOperation::Operator(
variable.clone(),
Operator::Add,
Operand::Variable(variable.clone()),
Operand::Variable(step_name),
));
res.push(MindustryOperation::Jump(start_label));
res.push(MindustryOperation::JumpLabel(end_label));
}
Instr::While(condition, instructions) => {
let start_label = namer.label("start");
let end_label = namer.label("end");
let condition_name = namer.temporary();
// Loop condition
res.push(MindustryOperation::JumpLabel(start_label.clone()));
res.append(&mut translate_expression(
condition,
namer,
condition_name.clone(),
));
res.push(MindustryOperation::JumpIf(
end_label.clone(),
Operator::Eq,
Operand::Variable(condition_name),
Operand::Variable(String::from("false")),
));
// Loop body
res.append(&mut translate_ast(instructions, namer, config));
// Loop end
res.push(MindustryOperation::Jump(start_label));
res.push(MindustryOperation::JumpLabel(end_label));
}
Instr::DoWhile(condition, instructions) => {
let start_label = namer.label("start");
let condition_name = namer.temporary();
// Loop start
res.push(MindustryOperation::JumpLabel(start_label.clone()));
// Loop body
res.append(&mut translate_ast(instructions, namer, config));
// Loop condition
res.append(&mut translate_expression(
condition,
namer,
condition_name.clone(),
));
res.push(MindustryOperation::JumpIf(
start_label,
Operator::Lte,
Operand::Variable(variable.clone()),
Operand::Variable(end_name),
Operator::Eq,
Operand::Variable(condition_name),
Operand::Variable(String::from("true")),
));
}
Instr::Print(expressions) => {

@ -0,0 +1,42 @@
set X 7
main__label_0_start:
set main__tmp_1 X
set main__tmp_2 1
op notEqual main__tmp_0 main__tmp_1 main__tmp_2
jump main__label_1_end equal main__tmp_0 false
main__label_2_start:
set main__tmp_6 X
set main__tmp_7 2
op mod main__tmp_4 main__tmp_6 main__tmp_7
set main__tmp_5 1
op equal main__tmp_3 main__tmp_4 main__tmp_5
jump main__label_3_end equal main__tmp_3 false
set main__tmp_10 3
set main__tmp_11 X
op mul main__tmp_8 main__tmp_10 main__tmp_11
set main__tmp_9 1
op add X main__tmp_8 main__tmp_9
set main__tmp_12 X
print main__tmp_12
set main__tmp_13 " "
print main__tmp_13
jump main__label_2_start always 0 0
main__label_3_end:
main__label_4_start:
set main__tmp_15 X
set main__tmp_16 2
op div X main__tmp_15 main__tmp_16
set main__tmp_17 X
print main__tmp_17
set main__tmp_18 " "
print main__tmp_18
set main__tmp_21 X
set main__tmp_22 2
op mod main__tmp_19 main__tmp_21 main__tmp_22
set main__tmp_20 0
op equal main__tmp_14 main__tmp_19 main__tmp_20
jump main__label_4_start equal main__tmp_14 true
jump main__label_0_start always 0 0
main__label_1_end:
set main__tmp_23 message1
printflush main__tmp_23

@ -0,0 +1,21 @@
set X 7
main__label_0_start:
jump main__label_1_end equal X 1
main__label_2_start:
op mod main__tmp_4 X 2
jump main__label_3_end notEqual main__tmp_4 1
op mul main__tmp_8 3 X
op add X main__tmp_8 1
print X
print " "
jump main__label_2_start always 0 0
main__label_3_end:
main__label_4_start:
op div X X 2
print X
print " "
op mod main__tmp_19 X 2
jump main__label_4_start equal main__tmp_19 0
jump main__label_0_start always 0 0
main__label_1_end:
printflush message1
Loading…
Cancel
Save