GOSUB and RETURN, fix condition names

main
Shad Amethyst 1 year ago
parent c2ad0ad08c
commit ed5de3aaa1

@ -205,3 +205,7 @@ DO WHILE x % 2 == 1
x = 3*x + 1
LOOP
```
## Subroutines
Subroutines allow you to execute the same piece of code from

@ -0,0 +1,32 @@
a = 0
b = 0
c = 0
a = 1
GOSUB sub1
c = c * 2
a = 2
GOTO end_loop
sub1:
b = 1
GOSUB sub2
b = b * 2
RETURN
b = 5
END
end_loop:
IF a == 2 AND b == 2 AND c == 2 THEN
PRINT "success"
ELSE
PRINT "fail: ", a, ", ", b, ", ", c
END IF
PRINT_FLUSH(message1)
GOTO end_loop
sub2:
c = 1
RETURN
c = 5

@ -0,0 +1,25 @@
a = 0
b = 0
c = 0
GOSUB sub
b = 1
c = 1
GOTO trap
END
sub:
a = 1
RETURN
a = 2
trap:
IF a == 1 AND b == 1 AND c == 1 THEN
PRINT "success"
ELSE
PRINT "fail: ", a, ", ", b, ", ", c
END IF
PRINT_FLUSH(message1)
GOTO trap

@ -1,23 +1,20 @@
REM This is an example of a more complex program,
REM which spawns procedural waves
REM This is an example of a more complex program, which spawns procedural waves.
REM The waves become progressively harder, and the wave delay is controlled by `cell1[0]`
LET wave = 0
LET timeout = @time + 120000
initial_wait:
DO
LET remaining = timeout - @time
IF remaining <= 0 THEN
GOTO main
END IF
PRINT "[red]Enemies[white] approaching: "
PRINT ceil(remaining / 1000), " s"
PRINT_FLUSH_GLOBAL(mission)
wait(0.5)
LOOP WHILE remaining > 0
GOTO initial_wait
main:
WHILE true
wave = wave + 1
REM TODO: difficult control wave multiplier
LET progression = POW(wave / 4, 0.75)
@ -32,33 +29,27 @@ main:
LET air_units = units - tank_units - mech_units
LET spawnx = 30
LET spawnairx = 30
LET spawny = 50
GOTO spawn_tank
spawn_tank_end:
GOTO spawn_mech
spawn_mech_end:
LET spawnx = 20
GOTO spawn_air
spawn_air_end:
GOSUB spawn_tank
GOSUB spawn_mech
GOSUB spawn_air
WRITE(wave, cell1, 1)
READ(timeout, cell1, 0)
timeout = @time + timeout * 1000
main_wait:
DO
LET remaining = timeout - @time
IF remaining <= 0 THEN
GOTO main
END IF
PRINT "[yellow]Wave ", wave, "[white] - "
PRINT "Next wave: ", ceil(remaining / 1000), " s"
PRINT_FLUSH_GLOBAL(mission)
wait(0.5)
GOTO main_wait
LOOP WHILE remaining > 0
WEND
spawn_tank:
FOR spawned = 1 TO tank_units
@ -92,7 +83,7 @@ spawn_tank:
END IF
NEXT spawned
GOTO spawn_tank_end
RETURN
spawn_mech:
FOR spawned = 1 TO mech_units
@ -125,28 +116,29 @@ spawn_mech:
END IF
NEXT spawned
GOTO spawn_mech_end
RETURN
spawn_air:
FOR spawned = 1 TO air_units
LET roll = rand(progression)
IF roll >= 3 THEN
IF roll >= 4 THEN
SPAWN(@disrupt, spawnx, spawny, 0, @crux, _)
spawnx = spawnx - 5.75
SPAWN(@disrupt, spawnairx, spawny, 0, @crux, _)
spawnairx = spawnairx - 5.75
ELSE
SPAWN(@quell, spawnx, spawny, 0, @crux, _)
spawnx = spawnx - 4.5
SPAWN(@quell, spawnairx, spawny, 0, @crux, _)
spawnairx = spawnairx - 4.5
END IF
ELSE
IF roll >= 2 THEN
SPAWN(@obviate, spawnx, spawny, 0, @crux, _)
spawnx = spawnx - 3.12
SPAWN(@obviate, spawnairx, spawny, 0, @crux, _)
spawnairx = spawnairx - 3.12
ELSE
IF roll >= 1 THEN
SPAWN(@avert, spawnx, spawny, 0, @crux, _)
spawnx = spawnx - 1
SPAWN(@avert, spawnairx, spawny, 0, @crux, _)
spawnairx = spawnairx - 1
ELSE
REM The elude is the only non-air unit
SPAWN(@elude, spawnx, spawny, 0, @crux, _)
spawnx = spawnx - 1
END IF
@ -156,6 +148,9 @@ spawn_air:
IF spawnx < 10 THEN
spawnx = 10
END IF
IF spawnairx < 5 THEN
spawnairx = 5
END IF
NEXT spawned
GOTO spawn_air_end
RETURN

@ -246,6 +246,18 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
tokens.take(1);
instructions.push(BasicAstInstruction::End);
}
[BasicToken::GoSub, BasicToken::Integer(label), ..] => {
tokens.take(2);
instructions.push(BasicAstInstruction::GoSub(label.to_string()));
}
[BasicToken::GoSub, BasicToken::Name(label), ..] => {
tokens.take(2);
instructions.push(BasicAstInstruction::GoSub(label.clone()));
}
[BasicToken::Return, ..] => {
tokens.take(1);
instructions.push(BasicAstInstruction::Return);
}
// == Misc ==
[BasicToken::Name(variable_name), BasicToken::Assign, ..] => {
tokens.take(2);

@ -20,6 +20,8 @@ pub enum BasicToken {
End,
Do,
Loop,
GoSub,
Return,
LabelEnd,
OpenParen,
CloseParen,
@ -80,7 +82,7 @@ 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|while)|print|for|to|step|next|while|do|wend|loop)(?:\s|$)").unwrap();
Regex::new(r"(?i)^(?:if|then|else|end\s?(?:if|while)|print|for|to|step|next|while|do|wend|loop|gosub|return|and|or)(?:\s|$)").unwrap();
let match_end = Regex::new(r"(?i)^end(?:\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();
@ -88,7 +90,7 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
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
@ -122,6 +124,10 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
"wend" => BasicToken::Wend,
"end while" => BasicToken::Wend,
"loop" => BasicToken::Loop,
"gosub" => BasicToken::GoSub,
"return" => BasicToken::Return,
"and" => BasicToken::Operator(Operator::And),
"or" => BasicToken::Operator(Operator::Or),
_ => unreachable!("{}", word),
}),
match_end => (BasicToken::End),
@ -133,6 +139,7 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
"+" => Operator::Add,
"-" => Operator::Sub,
"*" => Operator::Mul,
"//" => Operator::IDiv,
"/" => Operator::Div,
"%" => Operator::Mod,
"<" => Operator::Lt,
@ -143,6 +150,8 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
">>" => Operator::RShift,
"==" => Operator::Eq,
"<>" | "!=" => Operator::Neq,
"&&" => Operator::And,
"||" => Operator::Or,
_ => unreachable!(),
})),
match_assign => (BasicToken::Assign),

@ -13,7 +13,13 @@ pub enum BasicAstExpression {
#[derive(Clone, Debug, PartialEq)]
pub enum BasicAstInstruction {
JumpLabel(String),
/// Immediately and always jump to the label or line number stored
Jump(String),
/// Stores the return address, and jumps to the given label
GoSub(String),
/// Reads the return address and jumps to it
Return,
/// Stops the program, returning to the starting point
End,
Assign(String, BasicAstExpression),
IfThenElse(BasicAstExpression, BasicAstBlock, BasicAstBlock),

@ -3,6 +3,7 @@ pub enum Operator {
Add,
Sub,
Mul,
IDiv,
Div,
Mod,
RShift,
@ -16,18 +17,19 @@ pub enum Operator {
Max,
Min,
Pow,
// etc.
And,
Or,
}
impl Operator {
pub(crate) fn precedence(self) -> u8 {
use Operator as O;
match self {
O::Add | O::Sub => 3,
O::RShift | O::LShift => 4,
O::Mod => 5,
O::Mul | O::Div => 10,
O::Eq | O::Neq | O::Gt | O::Lt | O::Gte | O::Lte => 0,
Self::Add | Self::Sub => 3,
Self::RShift | Self::LShift => 4,
Self::Mod => 5,
Self::Mul | Self::Div | Self::IDiv => 10,
Self::Eq | Self::Neq | Self::Gt | Self::Lt | Self::Gte | Self::Lte => 1,
Self::And | Self::Or => 0,
_ => 128,
}
}
@ -54,12 +56,15 @@ pub(crate) fn format_operator(operator: Operator) -> &'static str {
Operator::Sub => "sub",
Operator::Mul => "mul",
Operator::Div => "div",
Operator::IDiv => "idiv",
Operator::Mod => "mod",
Operator::RShift => "shr",
Operator::LShift => "shl",
Operator::Max => "max",
Operator::Min => "min",
Operator::Pow => "pow",
Operator::And => "and",
Operator::Or => "or",
}
}
@ -70,6 +75,7 @@ pub enum UnaryOperator {
Ceil,
Rand,
Sqrt,
// Not,
}
impl TryFrom<&str> for UnaryOperator {

@ -132,9 +132,9 @@ fn format_condition(operator: Operator) -> &'static str {
Operator::Eq => "equal",
Operator::Neq => "notEqual",
Operator::Lt => "lessThan",
Operator::Lte => "lessThanEqual",
Operator::Lte => "lessThanEq",
Operator::Gt => "greaterThan",
Operator::Gte => "greaterThanEqual",
Operator::Gte => "greaterThanEq",
x => {
panic!("Operator {:?} is not a condition!", x);
}

@ -4,6 +4,8 @@ use crate::prelude::*;
mod display;
const MAX_INSTRUCTION_COUNT: usize = 1000;
pub struct Namer {
var_index: usize,
label_index: usize,
@ -125,6 +127,7 @@ pub fn translate_ast(
) -> MindustryProgram {
use crate::repr::basic::BasicAstInstruction as Instr;
let mut res = MindustryProgram::new();
let mut has_return = false;
for instruction in basic_ast.instructions.iter() {
match instruction {
@ -134,6 +137,45 @@ pub fn translate_ast(
Instr::Jump(to) => {
res.push(MindustryOperation::Jump(to.clone()));
}
Instr::GoSub(to) => {
let return_label = namer.label("return__phantom");
res.push(MindustryOperation::Operator(
String::from("__gosub_retaddr"),
Operator::Mul,
Operand::Variable(String::from("__gosub_retaddr")),
Operand::Integer(MAX_INSTRUCTION_COUNT as i64))
);
res.push(MindustryOperation::Operator(
String::from("__gosub_retaddr"),
Operator::Add,
Operand::Variable(String::from("__gosub_retaddr")),
Operand::Variable(String::from("@counter")),
));
res.push(MindustryOperation::Jump(to.clone()));
res.push(MindustryOperation::JumpLabel(return_label));
}
Instr::Return => {
res.push(MindustryOperation::Operator(
String::from("__return"),
Operator::Mod,
Operand::Variable(String::from("__gosub_retaddr")),
Operand::Integer(MAX_INSTRUCTION_COUNT as i64))
);
res.push(MindustryOperation::Operator(
String::from("__gosub_retaddr"),
Operator::IDiv,
Operand::Variable(String::from("__gosub_retaddr")),
Operand::Integer(MAX_INSTRUCTION_COUNT as i64))
);
res.push(MindustryOperation::Operator(
String::from("@counter"),
Operator::Add,
Operand::Variable(String::from("__return")),
Operand::Integer(1))
);
// Add a guard at the beginning of the program, to clear out the return address
has_return = true;
}
Instr::End => {
res.push(MindustryOperation::End);
}
@ -371,5 +413,9 @@ pub fn translate_ast(
}
}
if has_return {
res.0.insert(0, MindustryOperation::Set(String::from("__gosub_retaddr"), Operand::Integer(0)));
}
res
}

@ -0,0 +1,51 @@
set __gosub_retaddr 0
set a 0
set b 0
set c 0
op mul __gosub_retaddr __gosub_retaddr 1000
op add __gosub_retaddr __gosub_retaddr @counter
jump sub always 0 0
main__label_0_return__phantom:
set b 1
set c 1
jump trap always 0 0
end
sub:
set a 1
op mod __return __gosub_retaddr 1000
op idiv __gosub_retaddr __gosub_retaddr 1000
op add @counter __return 1
set a 2
trap:
set main__tmp_5 a
set main__tmp_6 1
op equal main__tmp_3 main__tmp_5 main__tmp_6
set main__tmp_7 b
set main__tmp_8 1
op equal main__tmp_4 main__tmp_7 main__tmp_8
op and main__tmp_1 main__tmp_3 main__tmp_4
set main__tmp_9 c
set main__tmp_10 1
op equal main__tmp_2 main__tmp_9 main__tmp_10
op and main__tmp_0 main__tmp_1 main__tmp_2
jump main__label_1_else notEqual main__tmp_0 true
set main__tmp_11 "success"
print main__tmp_11
jump main__label_2_endif always 0 0
main__label_1_else:
set main__tmp_12 "fail: "
print main__tmp_12
set main__tmp_13 a
print main__tmp_13
set main__tmp_14 ", "
print main__tmp_14
set main__tmp_15 b
print main__tmp_15
set main__tmp_16 ", "
print main__tmp_16
set main__tmp_17 c
print main__tmp_17
main__label_2_endif:
set main__tmp_18 message1
printflush main__tmp_18
jump trap always 0 0

@ -0,0 +1,37 @@
set __gosub_retaddr 0
set a 0
set b 0
set c 0
op mul __gosub_retaddr __gosub_retaddr 1000
op add __gosub_retaddr __gosub_retaddr @counter
jump sub always 0 0
main__label_0_return__phantom:
set b 1
set c 1
jump trap always 0 0
end
sub:
set a 1
op mod __return __gosub_retaddr 1000
op idiv __gosub_retaddr __gosub_retaddr 1000
op add @counter __return 1
set a 2
trap:
op equal main__tmp_3 a 1
op equal main__tmp_4 b 1
op and main__tmp_1 main__tmp_3 main__tmp_4
op equal main__tmp_2 c 1
op and main__tmp_0 main__tmp_1 main__tmp_2
jump main__label_1_else notEqual main__tmp_0 true
print "success"
jump main__label_2_endif always 0 0
main__label_1_else:
print "fail: "
print a
print ", "
print b
print ", "
print c
main__label_2_endif:
printflush message1
jump trap always 0 0

@ -1,5 +1,5 @@
print "Hello, world"
jump main__label_0_else greaterThanEqual method 2
jump main__label_0_else greaterThanEq method 2
jump main__label_2_else notEqual method 0
message mission
jump main__label_1_endif always 0 0

Loading…
Cancel
Save