use crate::errors::SourceLocation;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Integer,
Float,
String,
Boolean,
List(Box<Type>),
Map(Box<Type>), Buffer,
File,
Time,
Timer,
Value,
Thing(String),
Void,
Unknown,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ThingDef {
pub name: String,
pub fields: Vec<FieldDef>,
pub members: Vec<String>,
pub line: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FieldDef {
pub name: String,
pub field_type: Type,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FlagValueType {
Boolean,
Number,
Text,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileMode {
Reading,
Writing,
Appending,
}
#[derive(Debug, Clone)]
pub enum Expr {
IntegerLit(i64),
FloatLit(f64),
StringLit(String),
BoolLit(bool),
NothingLit,
Identifier(String),
BinaryOp {
left: Box<Expr>,
op: BinaryOperator,
right: Box<Expr>,
},
UnaryOp {
op: UnaryOperator,
operand: Box<Expr>,
},
Range {
start: Box<Expr>,
end: Box<Expr>,
inclusive: bool,
},
PropertyCheck {
value: Box<Expr>,
property: Property,
},
TypeCheck {
value: Box<Expr>,
type_noun: Type,
},
FunctionCall {
name: String,
args: Vec<Expr>,
},
ListLit {
elements: Vec<Expr>,
},
MapLit {
pairs: Vec<(Expr, Expr)>,
},
#[allow(dead_code)]
ListAccess {
list: Box<Expr>,
index: Box<Expr>,
},
PropertyAccess {
object: String,
property: ObjectProperty,
},
ThingField {
base: String,
path: Vec<String>,
},
MapAccess {
map: String,
key: Box<Expr>,
},
#[allow(dead_code)]
LastError,
ArgumentCount,
ArgumentAt {
index: Box<Expr>,
},
ArgumentName, ArgumentFirst, ArgumentSecond, ArgumentLast, ArgumentEmpty, ArgumentAll, ArgumentRaw, ArgumentHas {
value: Box<Expr>,
},
TreatingAs {
value: Box<Expr>,
match_value: Box<Expr>,
replacement: Box<Expr>,
},
EnvironmentVariable {
name: Box<Expr>,
},
EnvironmentVariableCount,
EnvironmentVariableAt {
index: Box<Expr>,
},
EnvironmentVariableExists {
name: Box<Expr>,
},
EnvironmentVariableFirst, EnvironmentVariableLast, EnvironmentVariableEmpty,
CurrentTime, Fork, ReapChild { pid: Option<Box<Expr>>, no_hang: bool, },
ReapedStatus,
Cast {
value: Box<Expr>,
target_type: Type,
radix: u32, },
DurationCast {
value: Box<Expr>,
unit: TimeUnit,
},
ByteAccess {
buffer: Box<Expr>,
index: Box<Expr>,
},
ElementAccess {
list: Box<Expr>,
index: Box<Expr>,
},
FormatString {
parts: Vec<FormatPart>,
},
FileAvailable {
path: Box<Expr>,
},
}
#[derive(Debug, Clone)]
pub enum FormatPart {
Literal(String),
Variable { name: String, format: Option<String> },
Expression { expr: Box<Expr>, format: Option<String> },
}
#[derive(Debug, Clone)]
pub enum TimeUnit {
Seconds,
Milliseconds,
}
#[derive(Debug, Clone)]
pub enum BinaryOperator {
Add, Subtract, Multiply, Divide, Modulo,
Equal, NotEqual, Greater, Less, GreaterEqual, LessEqual,
And, Or,
BitAnd, BitOr, BitXor, ShiftLeft, ShiftRight,
}
#[derive(Debug, Clone)]
pub enum UnaryOperator {
Negate,
Not,
}
#[derive(Debug, Clone)]
pub enum Property {
Even,
Odd,
Positive,
Negative,
Zero,
Empty,
}
#[derive(Debug, Clone)]
pub enum ObjectProperty {
Size, Capacity, Empty, Full,
Descriptor, Modified, Accessed, Permissions, Readable, Writable,
First, Last,
Keys, Values,
Absolute, Sign, Even, Odd, Positive, Negative, Zero,
Hour, Minute, Second, Day, Month, Year, Unix,
Duration, Elapsed, StartTime, EndTime, Running,
Type, }
#[derive(Debug, Clone)]
pub enum Statement {
Print {
value: Expr,
without_newline: bool,
},
VarDecl {
name: String,
var_type: Option<Type>,
value: Option<Expr>,
},
ThingDecl(ThingDef),
SetThingField {
base: String,
path: Vec<String>,
value: Expr,
},
FlagSchemaDecl {
name: String,
short: String,
long: String,
value_type: FlagValueType,
required: bool,
default: Option<Expr>,
},
ParseFlags,
Assignment {
name: String,
value: Expr,
},
ValueRetype {
name: String,
target_type: Type,
},
If {
condition: Expr,
then_block: Vec<Statement>,
else_if_blocks: Vec<(Expr, Vec<Statement>)>,
else_block: Option<Vec<Statement>>,
},
While {
condition: Expr,
body: Vec<Statement>,
},
ForRange {
variable: String,
range: Expr,
body: Vec<Statement>,
},
ForEach {
variable: String,
collection: Expr,
body: Vec<Statement>,
},
Repeat {
count: Expr,
body: Vec<Statement>,
},
Break,
Continue,
Exit {
code: Expr,
},
Return {
value: Option<Expr>,
declared_type: Option<Type>,
},
FunctionDef {
name: String,
params: Vec<(String, Type)>,
#[allow(dead_code)]
return_type: Type,
body: Vec<Statement>,
body_ended_early: Option<SourceLocation>,
body_ended_via_return: Option<SourceLocation>,
},
FunctionCall {
name: String,
args: Vec<Expr>,
},
Allocate {
name: String,
size: Expr,
},
Free {
name: String,
},
Increment {
name: String,
},
Decrement {
name: String,
},
BufferDecl {
name: String,
size: Expr,
},
ByteSet {
buffer: String,
index: Expr,
value: Expr,
},
ElementSet {
list: String,
index: Expr,
value: Expr,
},
MapSet {
map: String,
key: Expr,
value: Expr,
},
ListAppend {
list: String,
value: Expr,
},
BufferCopy {
source: Expr,
destination: String,
},
BufferClear {
name: String,
},
FileOpen {
name: String,
path: Expr,
mode: FileMode,
},
FileRead {
source: String, buffer: String,
},
FileReadLine {
source: String, buffer: String,
},
FileSeekLine {
file: String,
line: Expr, },
FileSeekByte {
file: String,
byte: Expr, },
FileWrite {
file: String,
value: Expr,
},
FileWriteNewline {
file: String,
},
FileClose {
file: String,
},
FileDelete {
path: Expr,
},
Rmdir {
path: Expr,
},
OnError {
actions: Vec<Statement>,
},
BufferResize {
name: String,
new_size: Expr,
},
LibraryDecl {
name: String,
version: String,
},
See {
path: String,
lib_name: Option<String>,
lib_version: Option<String>,
},
TimerDecl {
name: String,
},
TimerStart {
name: String,
},
TimerStop {
name: String,
},
Wait {
duration: Expr,
unit: TimeUnit,
},
GetTime {
into: String,
},
Mkdir {
path: Expr,
},
Chdir {
path: Expr,
},
Symlink {
target: Expr,
linkpath: Expr,
},
Mknod {
path: Expr,
node_type: DeviceNodeType,
major: Expr,
minor: Expr,
},
Mount {
source: Expr,
target: Expr,
fstype: Expr,
options: Option<Expr>,
},
Unmount {
target: Expr,
lazy: bool,
},
Shutdown,
Reboot,
Halt,
PivotRoot {
new_root: Expr,
put_old: Expr,
},
Execute {
path: Expr,
args: Expr, },
SendSignal {
signal: Expr,
pid: Expr,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceNodeType {
Character, Block, Fifo, }
#[derive(Debug, Clone)]
pub struct Program {
pub statements: Vec<Statement>,
pub uses_heap: bool,
pub uses_strings: bool,
pub uses_io: bool,
pub uses_args: bool,
pub things: Vec<ThingDef>,
}
impl Program {
pub fn new(statements: Vec<Statement>) -> Self {
let things = statements
.iter()
.filter_map(|s| match s {
Statement::ThingDecl(def) => Some(def.clone()),
_ => None,
})
.collect();
Program {
statements,
uses_heap: false,
uses_strings: false,
uses_io: false,
uses_args: false,
things,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DefiniteDeclKind {
Plain,
Buffer,
List,
Map,
File,
}
pub fn collect_definite_decls(stmts: &[Statement]) -> std::collections::HashMap<String, DefiniteDeclKind> {
collect_definite_decls_inner(stmts).kinds
}
struct DefiniteDecls {
kinds: std::collections::HashMap<String, DefiniteDeclKind>,
poisoned: std::collections::HashSet<String>,
untyped: std::collections::HashSet<String>,
}
impl DefiniteDecls {
fn new() -> Self {
DefiniteDecls {
kinds: std::collections::HashMap::new(),
poisoned: std::collections::HashSet::new(),
untyped: std::collections::HashSet::new(),
}
}
fn record(&mut self, name: &str, kind: DefiniteDeclKind) {
if self.poisoned.contains(name) {
return;
}
match self.kinds.get(name) {
Some(existing) if *existing != kind => {
if self.untyped.remove(name) {
self.kinds.insert(name.to_string(), kind);
} else {
self.kinds.remove(name);
self.poisoned.insert(name.to_string());
}
}
_ => {
self.untyped.remove(name);
self.kinds.insert(name.to_string(), kind);
}
}
}
fn record_untyped_write(&mut self, name: &str) {
if self.poisoned.contains(name) || self.kinds.contains_key(name) {
return;
}
self.kinds.insert(name.to_string(), DefiniteDeclKind::Plain);
self.untyped.insert(name.to_string());
}
fn intersect_with(&mut self, other: &DefiniteDecls) {
self.kinds
.retain(|name, kind| other.kinds.get(name) == Some(kind));
let still_untyped: std::collections::HashSet<String> = self
.untyped
.iter()
.filter(|name| {
self.kinds.contains_key(name.as_str()) && other.untyped.contains(name.as_str())
})
.cloned()
.collect();
self.untyped = still_untyped;
}
}
fn collect_definite_decls_inner(stmts: &[Statement]) -> DefiniteDecls {
let mut decls = DefiniteDecls::new();
for stmt in stmts {
match stmt {
Statement::VarDecl { name, var_type, .. } => match var_type {
Some(Type::Buffer) => decls.record(name, DefiniteDeclKind::Buffer),
Some(Type::List(_)) => decls.record(name, DefiniteDeclKind::List),
Some(Type::Map(_)) => decls.record(name, DefiniteDeclKind::Map),
Some(_) => decls.record(name, DefiniteDeclKind::Plain),
None => decls.record_untyped_write(name),
},
Statement::BufferDecl { name, .. } => {
decls.record(name, DefiniteDeclKind::Buffer);
}
Statement::Allocate { name, .. } | Statement::TimerDecl { name } => {
decls.record(name, DefiniteDeclKind::Plain);
}
Statement::FileOpen { name, .. } => {
decls.record(name, DefiniteDeclKind::File);
}
Statement::GetTime { into } => {
decls.record(into, DefiniteDeclKind::Plain);
}
Statement::If { then_block, else_if_blocks, else_block: Some(else_block), .. } => {
let mut definite = collect_definite_decls_inner(then_block);
for (_, block) in else_if_blocks {
definite.intersect_with(&collect_definite_decls_inner(block));
}
definite.intersect_with(&collect_definite_decls_inner(else_block));
for (name, kind) in &definite.kinds {
if definite.untyped.contains(name) {
decls.record_untyped_write(name);
} else {
decls.record(name, *kind);
}
}
}
_ => {}
}
}
decls
}
pub fn collect_all_typed_decls(stmts: &[Statement]) -> std::collections::HashMap<String, Type> {
let mut out = std::collections::HashMap::new();
let mut poisoned: std::collections::HashSet<String> = std::collections::HashSet::new();
fn record(
out: &mut std::collections::HashMap<String, Type>,
poisoned: &mut std::collections::HashSet<String>,
name: &str,
ty: Type,
) {
if poisoned.contains(name) {
return;
}
match out.get(name) {
Some(existing) if *existing != ty => {
out.remove(name);
poisoned.insert(name.to_string());
}
_ => {
out.insert(name.to_string(), ty);
}
}
}
fn walk(
stmts: &[Statement],
out: &mut std::collections::HashMap<String, Type>,
poisoned: &mut std::collections::HashSet<String>,
) {
for stmt in stmts {
match stmt {
Statement::VarDecl { name, var_type: Some(t), .. } => {
record(out, poisoned, name, t.clone());
}
Statement::BufferDecl { name, .. } => {
record(out, poisoned, name, Type::Buffer);
}
Statement::If { then_block, else_if_blocks, else_block, .. } => {
walk(then_block, out, poisoned);
for (_, block) in else_if_blocks {
walk(block, out, poisoned);
}
if let Some(block) = else_block {
walk(block, out, poisoned);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => {
walk(body, out, poisoned);
}
Statement::OnError { actions } => {
walk(actions, out, poisoned);
}
Statement::FunctionDef { .. } => {}
_ => {}
}
}
}
walk(stmts, &mut out, &mut poisoned);
out
}
pub fn collect_widened_lists(stmts: &[Statement]) -> std::collections::HashSet<String> {
let mut out = std::collections::HashSet::new();
walk_widened_lists(stmts, &mut out);
out
}
pub fn nested_function_defs(stmt: &Statement) -> Vec<&Statement> {
let mut out = Vec::new();
walk_nested_function_defs(stmt, &mut out);
out
}
fn walk_nested_function_defs<'a>(stmt: &'a Statement, out: &mut Vec<&'a Statement>) {
fn walk<'a>(stmts: &'a [Statement], out: &mut Vec<&'a Statement>) {
for stmt in stmts {
if matches!(stmt, Statement::FunctionDef { .. }) {
out.push(stmt);
}
walk_nested_function_defs(stmt, out);
}
}
match stmt {
Statement::If { then_block, else_if_blocks, else_block, .. } => {
walk(then_block, out);
for (_, block) in else_if_blocks {
walk(block, out);
}
if let Some(block) = else_block {
walk(block, out);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. }
| Statement::FunctionDef { body, .. } => walk(body, out),
Statement::OnError { actions } => walk(actions, out),
_ => {}
}
}
pub fn any_function_widens_a_parameter(stmts: &[Statement]) -> bool {
fn body_widens(body: &[Statement], params: &std::collections::HashSet<&str>) -> bool {
body.iter().any(|stmt| match stmt {
Statement::ListAppend { list, .. } | Statement::ElementSet { list, .. } => {
params.contains(list.as_str())
}
Statement::If { then_block, else_if_blocks, else_block, .. } => {
body_widens(then_block, params)
|| else_if_blocks.iter().any(|(_, b)| body_widens(b, params))
|| else_block.as_ref().is_some_and(|b| body_widens(b, params))
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => body_widens(body, params),
Statement::OnError { actions } => body_widens(actions, params),
_ => false,
})
}
stmts.iter().any(|stmt| match stmt {
Statement::FunctionDef { params, body, .. } => {
let names: std::collections::HashSet<&str> =
params.iter().map(|(n, _)| n.as_str()).collect();
body_widens(body, &names)
}
_ => false,
})
}
pub fn collect_map_key_writers(stmts: &[Statement]) -> std::collections::HashSet<String> {
fn walk(stmts: &[Statement], out: &mut std::collections::HashSet<String>) {
for stmt in stmts {
match stmt {
Statement::MapSet { map, .. } => {
out.insert(map.clone());
}
Statement::If { then_block, else_if_blocks, else_block, .. } => {
walk(then_block, out);
for (_, block) in else_if_blocks {
walk(block, out);
}
if let Some(block) = else_block {
walk(block, out);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. }
| Statement::FunctionDef { body, .. } => walk(body, out),
Statement::OnError { actions } => walk(actions, out),
_ => {}
}
}
}
let mut out = std::collections::HashSet::new();
walk(stmts, &mut out);
out
}
pub fn collect_literal_collection_shapes(
stmts: &[Statement],
) -> (
std::collections::HashMap<String, std::collections::HashSet<String>>,
std::collections::HashMap<String, usize>,
) {
use std::collections::{HashMap, HashSet};
fn walk(
stmts: &[Statement],
counts: &mut HashMap<String, usize>,
keys: &mut HashMap<String, HashSet<String>>,
lens: &mut HashMap<String, usize>,
) {
for stmt in stmts {
match stmt {
Statement::VarDecl { name, value, .. } => {
*counts.entry(name.clone()).or_insert(0) += 1;
match value {
Some(Expr::MapLit { pairs }) => {
let mut set = HashSet::new();
let every_key_is_literal = pairs.iter().all(|(k, _)| match k {
Expr::StringLit(key) => {
set.insert(key.clone());
true
}
_ => false,
});
if every_key_is_literal {
keys.insert(name.clone(), set);
}
}
Some(Expr::ListLit { elements }) => {
lens.insert(name.clone(), elements.len());
}
_ => {}
}
}
Statement::If { then_block, else_if_blocks, else_block, .. } => {
walk(then_block, counts, keys, lens);
for (_, block) in else_if_blocks {
walk(block, counts, keys, lens);
}
if let Some(block) = else_block {
walk(block, counts, keys, lens);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. }
| Statement::FunctionDef { body, .. } => walk(body, counts, keys, lens),
Statement::OnError { actions } => walk(actions, counts, keys, lens),
_ => {}
}
}
}
let mut counts = HashMap::new();
let mut keys = HashMap::new();
let mut lens = HashMap::new();
walk(stmts, &mut counts, &mut keys, &mut lens);
keys.retain(|name, _| counts.get(name) == Some(&1));
lens.retain(|name, _| counts.get(name) == Some(&1));
(keys, lens)
}
pub fn any_function_writes_a_map_parameter(stmts: &[Statement]) -> bool {
fn body_writes(body: &[Statement], params: &std::collections::HashSet<&str>) -> bool {
body.iter().any(|stmt| match stmt {
Statement::MapSet { map, .. } => params.contains(map.as_str()),
Statement::If { then_block, else_if_blocks, else_block, .. } => {
body_writes(then_block, params)
|| else_if_blocks.iter().any(|(_, b)| body_writes(b, params))
|| else_block.as_ref().is_some_and(|b| body_writes(b, params))
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. } => body_writes(body, params),
Statement::OnError { actions } => body_writes(actions, params),
_ => false,
})
}
stmts.iter().any(|stmt| match stmt {
Statement::FunctionDef { params, body, .. } => {
let names: std::collections::HashSet<&str> =
params.iter().map(|(n, _)| n.as_str()).collect();
body_writes(body, &names)
}
_ => false,
})
}
fn walk_widened_lists(stmts: &[Statement], out: &mut std::collections::HashSet<String>) {
fn note_alias(expr: &Expr, out: &mut std::collections::HashSet<String>) {
if let Expr::Identifier(n) = expr {
out.insert(n.clone());
}
}
fn note_call_args(expr: &Expr, out: &mut std::collections::HashSet<String>) {
match expr {
Expr::FunctionCall { args, .. } => {
for arg in args {
note_alias(arg, out);
note_call_args(arg, out);
}
}
Expr::BinaryOp { left, right, .. } => {
note_call_args(left, out);
note_call_args(right, out);
}
Expr::UnaryOp { operand, .. } => note_call_args(operand, out),
Expr::Cast { value, .. } | Expr::TreatingAs { value, .. } => note_call_args(value, out),
Expr::ListLit { elements } => {
for e in elements {
note_call_args(e, out);
}
}
Expr::MapLit { pairs } => {
for (k, v) in pairs {
note_call_args(k, out);
note_call_args(v, out);
}
}
Expr::ElementAccess { list, index } | Expr::ListAccess { list, index } => {
note_call_args(list, out);
note_call_args(index, out);
}
Expr::FormatString { parts } => {
for part in parts {
if let FormatPart::Expression { expr, .. } = part {
note_call_args(expr, out);
}
}
}
_ => {}
}
}
for stmt in stmts {
match stmt {
Statement::ListAppend { list, value } => {
out.insert(list.clone());
note_call_args(value, out);
}
Statement::ElementSet { list, index, value } => {
out.insert(list.clone());
note_call_args(index, out);
note_call_args(value, out);
}
Statement::Assignment { name, value } => {
out.insert(name.clone());
note_alias(value, out);
note_call_args(value, out);
}
Statement::VarDecl { value: Some(value), .. } => {
note_alias(value, out);
note_call_args(value, out);
}
Statement::Return { value: Some(value), .. } => {
note_alias(value, out);
note_call_args(value, out);
}
Statement::FunctionCall { args, .. } => {
for arg in args {
note_alias(arg, out);
note_call_args(arg, out);
}
}
Statement::If { condition, then_block, else_if_blocks, else_block } => {
note_call_args(condition, out);
walk_widened_lists(then_block, out);
for (cond, block) in else_if_blocks {
note_call_args(cond, out);
walk_widened_lists(block, out);
}
if let Some(block) = else_block {
walk_widened_lists(block, out);
}
}
Statement::While { body, .. }
| Statement::ForRange { body, .. }
| Statement::ForEach { body, .. }
| Statement::Repeat { body, .. }
| Statement::FunctionDef { body, .. } => {
walk_widened_lists(body, out);
}
Statement::OnError { actions } => {
walk_widened_lists(actions, out);
}
Statement::Print { value, .. } => {
note_call_args(value, out);
}
_ => {}
}
}
}
pub const MIN_BUFFER_SIZE: i64 = 1;
pub const MAX_BUFFER_SIZE: i64 = 1024 * 1024 * 1024;
pub fn constant_integer(expr: &Expr) -> Option<i64> {
match expr {
Expr::IntegerLit(n) => Some(*n),
Expr::UnaryOp { op: UnaryOperator::Negate, operand } => {
constant_integer(operand)?.checked_neg()
}
Expr::BinaryOp { left, op, right } => {
let left = constant_integer(left)?;
let right = constant_integer(right)?;
match op {
BinaryOperator::Add => left.checked_add(right),
BinaryOperator::Subtract => left.checked_sub(right),
BinaryOperator::Multiply => left.checked_mul(right),
_ => None,
}
}
_ => None,
}
}
pub fn collect_constant_numbers(stmts: &[Statement]) -> std::collections::HashMap<String, i64> {
let mut declared: std::collections::HashMap<String, i64> = std::collections::HashMap::new();
let mut written: std::collections::HashSet<String> = std::collections::HashSet::new();
walk_constant_numbers(stmts, &mut declared, &mut written);
declared.retain(|name, _| !written.contains(name));
declared
}
fn walk_constant_numbers(
stmts: &[Statement],
declared: &mut std::collections::HashMap<String, i64>,
written: &mut std::collections::HashSet<String>,
) {
for stmt in stmts {
match stmt {
Statement::VarDecl { name, var_type, value } => {
if declared.contains_key(name) {
written.insert(name.clone());
}
match (var_type, value.as_ref().and_then(constant_integer)) {
(None | Some(Type::Integer), Some(n)) => {
declared.insert(name.clone(), n);
}
_ => {
written.insert(name.clone());
}
}
}
Statement::Assignment { name, .. }
| Statement::ValueRetype { name, .. }
| Statement::Increment { name }
| Statement::Decrement { name }
| Statement::Allocate { name, .. }
| Statement::BufferDecl { name, .. }
| Statement::TimerDecl { name }
| Statement::FileOpen { name, .. } => {
written.insert(name.clone());
}
Statement::GetTime { into } => {
written.insert(into.clone());
}
Statement::FlagSchemaDecl { name, .. } => {
written.insert(name.clone());
}
Statement::If { then_block, else_if_blocks, else_block, .. } => {
walk_constant_numbers(then_block, declared, written);
for (_, block) in else_if_blocks {
walk_constant_numbers(block, declared, written);
}
if let Some(block) = else_block {
walk_constant_numbers(block, declared, written);
}
}
Statement::ForRange { variable, body, .. } => {
written.insert(variable.clone());
walk_constant_numbers(body, declared, written);
}
Statement::ForEach { variable, body, .. } => {
written.insert(variable.clone());
walk_constant_numbers(body, declared, written);
}
Statement::While { body, .. } | Statement::Repeat { body, .. } => {
walk_constant_numbers(body, declared, written);
}
Statement::FunctionDef { params, body, .. } => {
for (param, _) in params {
written.insert(param.clone());
}
walk_constant_numbers(body, declared, written);
}
Statement::OnError { actions } => {
walk_constant_numbers(actions, declared, written);
}
_ => {}
}
}
}