use async_trait::async_trait;
use endbasic_core::ast::{BuiltinCallSpan, FunctionCallSpan, 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, span: &FunctionCallSpan, _symbols: &mut Symbols) -> FunctionResult {
if !span.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, span: &BuiltinCallSpan, machine: &mut Machine) -> CommandResult {
if span.args.len() != 1 {
return Err(CallError::SyntaxError);
}
let expr = span.args[0].expr.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(
expr.start_pos(),
"Light id cannot be zero or negative".to_owned(),
));
}
let i = i as usize;
if i > lights.len() {
return Err(CallError::ArgumentError(
expr.start_pos(),
"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(
expr.start_pos(),
"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");
loop {
match block_on(machine.exec(&mut INPUT.as_bytes())).expect("Execution error") {
StopReason::Eof => break,
StopReason::Exited(i) => println!("Script explicitly exited with code {}", i),
StopReason::Break => (), }
}
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);
}
}
}