use async_trait::async_trait;
use endbasic_core::ast::{ArgSep, Expr, Value, VarType};
use endbasic_core::exec::{Machine, StopReason};
use endbasic_core::syms::{
CallError, CallableMetadata, CallableMetadataBuilder, Command, CommandResult, Function,
FunctionResult, Symbols,
};
use futures_lite::future::block_on;
use std::cell::RefCell;
use std::rc::Rc;
const INPUT: &str = r#"
total = NUM_LIGHTS()
' Start by turning all lights on.
FOR i = 1 TO total
SWITCH_LIGHT i
NEXT
' And then switch every other light to off.
FOR i = 1 TO total STEP 2
SWITCH_LIGHT i
NEXT
"#;
type Lights = Vec<bool>;
struct NumLightsFunction {
metadata: CallableMetadata,
lights: Rc<RefCell<Lights>>,
}
impl NumLightsFunction {
pub fn new(lights: Rc<RefCell<Lights>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("NUM_LIGHTS", VarType::Integer)
.with_syntax("")
.with_category("Demonstration")
.with_description("Returns the number of available lights.")
.build(),
lights,
})
}
}
#[async_trait(?Send)]
impl Function for NumLightsFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, args: &[Expr], _symbols: &mut Symbols) -> FunctionResult {
if !args.is_empty() {
return Err(CallError::SyntaxError);
}
let num = self.lights.borrow().len();
assert!(num <= std::i32::MAX as usize, "Ended up with too many lights");
Ok(Value::Integer(num as i32))
}
}
struct SwitchLightCommand {
metadata: CallableMetadata,
lights: Rc<RefCell<Lights>>,
}
impl SwitchLightCommand {
pub fn new(lights: Rc<RefCell<Lights>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("SWITCH_LIGHT", VarType::Void)
.with_syntax("id")
.with_category("Demonstration")
.with_description("Turns the light identified by 'id' on or off.")
.build(),
lights,
})
}
}
#[async_trait(?Send)]
impl Command for SwitchLightCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> CommandResult {
if args.len() != 1 {
return Err(CallError::SyntaxError);
}
let expr = args[0].0.as_ref().expect("A single argument can never be empty");
match expr.eval(machine.get_mut_symbols()).await? {
Value::Integer(i) => {
let lights = &mut *self.lights.borrow_mut();
if i < 1 {
return Err(CallError::ArgumentError(
"Light id cannot be zero or negative".to_owned(),
));
}
let i = i as usize;
if i > lights.len() {
return Err(CallError::ArgumentError("Light id out of range".to_owned()));
}
if lights[i - 1] {
println!("Turning light {} off", i);
} else {
println!("Turning light {} on", i);
}
lights[i - 1] = !lights[i - 1];
}
_ => return Err(CallError::ArgumentError("Mismatched expression type".to_owned())),
}
Ok(())
}
}
fn main() {
let lights = Rc::from(RefCell::from(vec![false; 10]));
let mut machine = Machine::default();
machine.add_command(SwitchLightCommand::new(lights.clone()));
machine.add_function(NumLightsFunction::new(lights.clone()));
println!("Running script");
let result = block_on(machine.exec(&mut INPUT.as_bytes())).expect("Execution error");
assert!(result == StopReason::Eof, "We did not register an EXIT command");
println!("Script done. Dumping final lights state:");
for (i, on) in lights.borrow().iter().enumerate() {
if *on {
println!("Light {} is on", i + 1);
} else {
println!("Light {} is off", i + 1);
}
}
}