use async_trait::async_trait;
use endbasic_core::ast::ExprType;
use endbasic_core::compiler::{ArgSepSyntax, RequiredValueSyntax, SingularArgSyntax};
use endbasic_core::exec::{Machine, Scope, StopReason};
use endbasic_core::syms::{
CallError, CallResult, Callable, CallableMetadata, CallableMetadataBuilder,
};
use futures_lite::future::block_on;
use std::borrow::Cow;
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")
.with_return_type(ExprType::Integer)
.with_syntax(&[(&[], None)])
.with_category("Demonstration")
.with_description("Returns the number of available lights.")
.build(),
lights,
})
}
}
#[async_trait(?Send)]
impl Callable for NumLightsFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> CallResult {
debug_assert_eq!(0, scope.nargs());
let num = self.lights.borrow().len();
assert!(num <= i32::MAX as usize, "Ended up with too many lights");
scope.return_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")
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("id"), vtype: ExprType::Integer },
ArgSepSyntax::End,
)],
None,
)])
.with_category("Demonstration")
.with_description("Turns the light identified by 'id' on or off.")
.build(),
lights,
})
}
}
#[async_trait(?Send)]
impl Callable for SwitchLightCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> CallResult {
debug_assert_eq!(1, scope.nargs());
let (i, ipos) = scope.pop_integer_with_pos();
let lights = &mut *self.lights.borrow_mut();
if i < 1 {
return Err(CallError::ArgumentError(
ipos,
"Light id cannot be zero or negative".to_owned(),
));
}
let i = i as usize;
if i > lights.len() {
return Err(CallError::ArgumentError(ipos, "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];
Ok(())
}
}
fn main() {
let lights = Rc::from(RefCell::from(vec![false; 10]));
let mut machine = Machine::default();
machine.add_callable(NumLightsFunction::new(lights.clone()));
machine.add_callable(SwitchLightCommand::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);
}
}
}