-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathdsl.rs
More file actions
170 lines (151 loc) · 5.86 KB
/
dsl.rs
File metadata and controls
170 lines (151 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// EndBASIC
// Copyright 2021 Julio Merino
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//! A domain-specific language (DSL) to control some hypothetical light fixtures.
//!
//! This example sets up a minimal EndBASIC interpreter without any parts of the standard library
//! and registers a custom function `NUM_LIGHTS` to query the number of available lights and a
//! custom command `SWITCH_LIGHT` to flip the status of the given light. With these configured,
//! this example then runs a piece of EndBASIC code that uses these primitives to control the state
//! of the lights, and finally dumps the resulting state to the screen.
use async_trait::async_trait;
use endbasic_core::ast::ExprType;
use endbasic_core::compiler::{ArgSepSyntax, RequiredValueSyntax, SingularArgSyntax};
use endbasic_core::exec::{Error, Machine, Result, Scope, StopReason};
use endbasic_core::syms::{Callable, CallableMetadata, CallableMetadataBuilder};
use futures_lite::future::block_on;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
/// Sample code that uses our DSL to control the lights.
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>;
/// The `NUM_LIGHTS` function.
struct NumLightsFunction {
metadata: CallableMetadata,
lights: Rc<RefCell<Lights>>,
}
impl NumLightsFunction {
/// Creates a new function that queries the `lights` state.
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) -> Result<()> {
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)
}
}
/// The `SWITCH_LIGHT` command.
struct SwitchLightCommand {
metadata: CallableMetadata,
lights: Rc<RefCell<Lights>>,
}
impl SwitchLightCommand {
/// Creates a new command that modifies the `lights` state.
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) -> Result<()> {
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(Error::SyntaxError(ipos, "Light id cannot be zero or negative".to_owned()));
}
let i = i as usize;
if i > lights.len() {
return Err(Error::SyntaxError(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() {
// Create the state of the lights. We have to gate this state behind an Rc and a RefCell in
// order to pass it around the callable objects used by EndBASIC.
let lights = Rc::from(RefCell::from(vec![false; 10]));
// Create the EndBASIC machine and register our callable objects to create our DSL.
let mut machine = Machine::default();
machine.add_callable(NumLightsFunction::new(lights.clone()));
machine.add_callable(SwitchLightCommand::new(lights.clone()));
// Execute the sample script, which will call back into our callable objects in Rust land to
// manipulate the state of the lights.
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 => (), // Ignore signals.
}
}
// Finally, print out the resulting state to verify that it is what we expect.
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);
}
}
}