use crate::dynamic::{Dynamic, Union};
use crate::fn_native::{FnPtr, Shared};
use crate::module::{Module, ModuleRef};
use crate::syntax::FnCustomSyntaxEval;
use crate::token::{Position, Token, NO_POS};
use crate::utils::ImmutableString;
use crate::StaticVec;
use crate::INT;
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter, Map};
#[cfg(not(feature = "no_module"))]
use crate::engine::Imports;
use crate::stdlib::{
any::TypeId,
borrow::Cow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
num::NonZeroUsize,
ops::{Add, AddAssign},
string::String,
vec,
vec::Vec,
};
#[cfg(not(feature = "no_float"))]
use crate::stdlib::ops::Neg;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FnAccess {
Public,
Private,
}
impl fmt::Display for FnAccess {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Private => write!(f, "private"),
Self::Public => write!(f, "public"),
}
}
}
impl FnAccess {
#[inline(always)]
pub fn is_private(self) -> bool {
match self {
Self::Public => false,
Self::Private => true,
}
}
#[inline(always)]
pub fn is_public(self) -> bool {
match self {
Self::Public => true,
Self::Private => false,
}
}
}
#[derive(Debug, Clone)]
pub struct ScriptFnDef {
pub body: Stmt,
pub lib: Option<Shared<Module>>,
#[cfg(not(feature = "no_module"))]
pub mods: Imports,
pub name: ImmutableString,
pub access: FnAccess,
pub params: StaticVec<String>,
#[cfg(not(feature = "no_closure"))]
pub externals: HashSet<String>,
}
impl fmt::Display for ScriptFnDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}({})",
if self.access.is_private() {
"private "
} else {
""
},
self.name,
self.params
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(",")
)
}
}
#[derive(Debug, Clone, Default)]
pub struct AST(
Vec<Stmt>,
Module,
);
impl AST {
#[inline(always)]
pub fn new(statements: Vec<Stmt>, lib: Module) -> Self {
Self(statements, lib)
}
#[cfg(not(feature = "internals"))]
#[inline(always)]
pub(crate) fn statements(&self) -> &[Stmt] {
&self.0
}
#[cfg(feature = "internals")]
#[deprecated(note = "this method is volatile and may change")]
#[inline(always)]
pub fn statements(&self) -> &[Stmt] {
&self.0
}
#[cfg(not(feature = "no_optimize"))]
#[inline(always)]
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
&mut self.0
}
#[cfg(not(feature = "internals"))]
#[inline(always)]
pub(crate) fn lib(&self) -> &Module {
&self.1
}
#[cfg(feature = "internals")]
#[deprecated(note = "this method is volatile and may change")]
#[inline(always)]
pub fn lib(&self) -> &Module {
&self.1
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn clone_functions_only(&self) -> Self {
self.clone_functions_only_filtered(|_, _, _| true)
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn clone_functions_only_filtered(
&self,
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self {
let mut functions: Module = Default::default();
functions.merge_filtered(&self.1, &mut filter);
Self(Default::default(), functions)
}
#[inline(always)]
pub fn clone_statements_only(&self) -> Self {
Self(self.0.clone(), Default::default())
}
#[inline(always)]
pub fn merge(&self, other: &Self) -> Self {
self.merge_filtered(other, |_, _, _| true)
}
#[inline(always)]
pub fn combine(&mut self, other: Self) -> &mut Self {
self.combine_filtered(other, |_, _, _| true)
}
#[inline]
pub fn merge_filtered(
&self,
other: &Self,
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self {
let Self(statements, functions) = self;
let ast = match (statements.is_empty(), other.0.is_empty()) {
(false, false) => {
let mut statements = statements.clone();
statements.extend(other.0.iter().cloned());
statements
}
(false, true) => statements.clone(),
(true, false) => other.0.clone(),
(true, true) => vec![],
};
let mut functions = functions.clone();
functions.merge_filtered(&other.1, &mut filter);
Self::new(ast, functions)
}
#[inline(always)]
pub fn combine_filtered(
&mut self,
other: Self,
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self {
let Self(ref mut statements, ref mut functions) = self;
statements.extend(other.0.into_iter());
functions.merge_filtered(&other.1, &mut filter);
self
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) {
self.1.retain_functions(filter);
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn iter_functions<'a>(
&'a self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
self.1.iter_script_fn()
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn clear_functions(&mut self) {
self.1 = Default::default();
}
#[inline(always)]
pub fn clear_statements(&mut self) {
self.0 = vec![];
}
}
impl<A: AsRef<AST>> Add<A> for &AST {
type Output = AST;
#[inline(always)]
fn add(self, rhs: A) -> Self::Output {
self.merge(rhs.as_ref())
}
}
impl<A: Into<AST>> AddAssign<A> for AST {
#[inline(always)]
fn add_assign(&mut self, rhs: A) {
self.combine(rhs.into());
}
}
impl AsRef<[Stmt]> for AST {
#[inline(always)]
fn as_ref(&self) -> &[Stmt] {
self.statements()
}
}
impl AsRef<Module> for AST {
#[inline(always)]
fn as_ref(&self) -> &Module {
self.lib()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Ident {
pub name: String,
pub pos: Position,
}
impl Ident {
pub fn new(name: String, pos: Position) -> Self {
Self { name, pos }
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct IdentX {
pub name: ImmutableString,
pub pos: Position,
}
impl From<Ident> for IdentX {
fn from(value: Ident) -> Self {
Self {
name: value.name.into(),
pos: value.pos,
}
}
}
impl IdentX {
pub fn new(name: impl Into<ImmutableString>, pos: Position) -> Self {
Self {
name: name.into(),
pos,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum ReturnType {
Return,
Exception,
}
#[derive(Debug, Clone, Hash)]
pub enum Stmt {
Noop(Position),
IfThenElse(Expr, Box<(Stmt, Option<Stmt>)>, Position),
While(Expr, Box<Stmt>, Position),
Loop(Box<Stmt>, Position),
For(Expr, Box<(String, Stmt)>, Position),
Let(Box<Ident>, Option<Expr>, bool, Position),
Const(Box<Ident>, Option<Expr>, bool, Position),
Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position),
Block(Vec<Stmt>, Position),
TryCatch(Box<(Stmt, Option<Ident>, Stmt)>, Position, Position),
Expr(Expr),
Continue(Position),
Break(Position),
ReturnWithVal((ReturnType, Position), Option<Expr>, Position),
#[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<IdentX>>, Position),
#[cfg(not(feature = "no_module"))]
Export(Vec<(Ident, Option<Ident>)>, Position),
#[cfg(not(feature = "no_closure"))]
Share(Box<Ident>),
}
impl Default for Stmt {
#[inline(always)]
fn default() -> Self {
Self::Noop(NO_POS)
}
}
impl Stmt {
pub fn is_noop(&self) -> bool {
match self {
Self::Noop(_) => true,
_ => false,
}
}
pub fn position(&self) -> Position {
match self {
Self::Noop(pos)
| Self::Continue(pos)
| Self::Break(pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos)
| Self::While(_, _, pos)
| Self::Loop(_, pos)
| Self::For(_, _, pos)
| Self::ReturnWithVal((_, pos), _, _)
| Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos,
Self::Expr(x) => x.position(),
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, pos) => *pos,
#[cfg(not(feature = "no_module"))]
Self::Export(_, pos) => *pos,
#[cfg(not(feature = "no_closure"))]
Self::Share(x) => x.pos,
}
}
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
Self::Noop(pos)
| Self::Continue(pos)
| Self::Break(pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos)
| Self::While(_, _, pos)
| Self::Loop(_, pos)
| Self::For(_, _, pos)
| Self::ReturnWithVal((_, pos), _, _)
| Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos = new_pos,
Self::Expr(x) => {
x.set_position(new_pos);
}
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, pos) => *pos = new_pos,
#[cfg(not(feature = "no_module"))]
Self::Export(_, pos) => *pos = new_pos,
#[cfg(not(feature = "no_closure"))]
Self::Share(x) => x.pos = new_pos,
}
self
}
pub fn is_self_terminated(&self) -> bool {
match self {
Self::IfThenElse(_, _, _)
| Self::While(_, _, _)
| Self::Loop(_, _)
| Self::For(_, _, _)
| Self::Block(_, _)
| Self::TryCatch(_, _, _) => true,
Self::Noop(_) => false,
Self::Let(_, _, _, _)
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
| Self::Expr(_)
| Self::Continue(_)
| Self::Break(_)
| Self::ReturnWithVal(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => false,
}
}
pub fn is_pure(&self) -> bool {
match self {
Self::Noop(_) => true,
Self::Expr(expr) => expr.is_pure(),
Self::IfThenElse(condition, x, _) if x.1.is_some() => {
condition.is_pure() && x.0.is_pure() && x.1.as_ref().unwrap().is_pure()
}
Self::IfThenElse(condition, x, _) => condition.is_pure() && x.0.is_pure(),
Self::While(condition, block, _) => condition.is_pure() && block.is_pure(),
Self::Loop(block, _) => block.is_pure(),
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false,
Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(),
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => false,
}
}
}
#[derive(Clone)]
pub struct CustomExpr {
pub(crate) keywords: StaticVec<Expr>,
pub(crate) func: Shared<FnCustomSyntaxEval>,
}
impl fmt::Debug for CustomExpr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.keywords, f)
}
}
impl Hash for CustomExpr {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.keywords.hash(state);
}
}
impl CustomExpr {
#[inline(always)]
pub fn keywords(&self) -> &[Expr] {
&self.keywords
}
#[inline(always)]
pub fn func(&self) -> &FnCustomSyntaxEval {
self.func.as_ref()
}
}
#[cfg(not(feature = "no_float"))]
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub struct FloatWrapper(pub FLOAT);
#[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.0.to_le_bytes());
}
}
#[cfg(not(feature = "no_float"))]
impl Neg for FloatWrapper {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
#[cfg(not(feature = "no_float"))]
impl From<INT> for FloatWrapper {
fn from(value: INT) -> Self {
Self(value as FLOAT)
}
}
#[derive(Debug, Clone, Hash)]
pub struct BinaryExpr {
pub lhs: Expr,
pub rhs: Expr,
}
#[derive(Debug, Clone, Hash, Default)]
pub struct FnCallInfo {
pub hash: u64,
pub native_only: bool,
pub capture: bool,
pub def_value: Option<bool>,
pub namespace: Option<Box<ModuleRef>>,
pub name: Cow<'static, str>,
pub args: StaticVec<Expr>,
}
#[derive(Debug, Clone, Hash)]
pub enum Expr {
IntegerConstant(INT, Position),
#[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper, Position),
CharConstant(char, Position),
StringConstant(Box<IdentX>),
FnPointer(Box<IdentX>),
Variable(Box<(Option<NonZeroUsize>, Option<Box<ModuleRef>>, u64, Ident)>),
Property(Box<((String, String), IdentX)>),
Stmt(Box<StaticVec<Stmt>>, Position),
Expr(Box<Expr>),
FnCall(Box<FnCallInfo>, Position),
Dot(Box<BinaryExpr>, Position),
Index(Box<BinaryExpr>, Position),
Array(Box<StaticVec<Expr>>, Position),
Map(Box<StaticVec<(IdentX, Expr)>>, Position),
In(Box<BinaryExpr>, Position),
And(Box<BinaryExpr>, Position),
Or(Box<BinaryExpr>, Position),
True(Position),
False(Position),
Unit(Position),
Custom(Box<CustomExpr>, Position),
}
impl Default for Expr {
#[inline(always)]
fn default() -> Self {
Self::Unit(NO_POS)
}
}
impl Expr {
pub fn get_type_id(&self) -> Option<TypeId> {
Some(match self {
Self::Expr(x) => return x.get_type_id(),
Self::IntegerConstant(_, _) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => TypeId::of::<FLOAT>(),
Self::CharConstant(_, _) => TypeId::of::<char>(),
Self::StringConstant(_) => TypeId::of::<ImmutableString>(),
Self::FnPointer(_) => TypeId::of::<FnPtr>(),
Self::True(_) | Self::False(_) | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) => {
TypeId::of::<bool>()
}
Self::Unit(_) => TypeId::of::<()>(),
#[cfg(not(feature = "no_index"))]
Self::Array(_, _) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))]
Self::Map(_, _) => TypeId::of::<Map>(),
_ => return None,
})
}
pub fn get_constant_value(&self) -> Option<Dynamic> {
Some(match self {
Self::Expr(x) => return x.get_constant_value(),
Self::IntegerConstant(x, _) => (*x).into(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(x, _) => x.0.into(),
Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x) => x.name.clone().into(),
Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked(
x.name.clone(),
Default::default(),
)))),
Self::True(_) => true.into(),
Self::False(_) => false.into(),
Self::Unit(_) => ().into(),
#[cfg(not(feature = "no_index"))]
Self::Array(x, _) if x.iter().all(Self::is_constant) => Dynamic(Union::Array(
Box::new(x.iter().map(|v| v.get_constant_value().unwrap()).collect()),
)),
#[cfg(not(feature = "no_object"))]
Self::Map(x, _) if x.iter().all(|(_, v)| v.is_constant()) => {
Dynamic(Union::Map(Box::new(
x.iter()
.map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap()))
.collect(),
)))
}
_ => return None,
})
}
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
match self {
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.3).name.as_str()),
_ => None,
}
}
pub fn position(&self) -> Position {
match self {
Self::Expr(x) => x.position(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos,
Self::IntegerConstant(_, pos) => *pos,
Self::CharConstant(_, pos) => *pos,
Self::StringConstant(x) => x.pos,
Self::FnPointer(x) => x.pos,
Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos,
Self::Property(x) => (x.1).pos,
Self::Stmt(_, pos) => *pos,
Self::Variable(x) => (x.3).pos,
Self::FnCall(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos,
Self::Dot(x, _) | Self::Index(x, _) => x.lhs.position(),
Self::Custom(_, pos) => *pos,
}
}
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
Self::Expr(x) => {
x.set_position(new_pos);
}
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos = new_pos,
Self::IntegerConstant(_, pos) => *pos = new_pos,
Self::CharConstant(_, pos) => *pos = new_pos,
Self::StringConstant(x) => x.pos = new_pos,
Self::FnPointer(x) => x.pos = new_pos,
Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.3).pos = new_pos,
Self::Property(x) => (x.1).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
Self::Custom(_, pos) => *pos = new_pos,
}
self
}
pub fn is_pure(&self) -> bool {
match self {
Self::Expr(x) => x.is_pure(),
Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure),
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => {
x.lhs.is_pure() && x.rhs.is_pure()
}
Self::Stmt(x, _) => x.iter().all(Stmt::is_pure),
Self::Variable(_) => true,
_ => self.is_constant(),
}
}
#[inline(always)]
pub fn is_unit(&self) -> bool {
match self {
Self::Unit(_) => true,
_ => false,
}
}
pub fn is_literal(&self) -> bool {
match self {
Self::Expr(x) => x.is_literal(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => true,
Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_)
| Self::FnPointer(_)
| Self::True(_)
| Self::False(_)
| Self::Unit(_) => true,
Self::Array(x, _) => x.iter().all(Self::is_literal),
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_literal),
Self::In(x, _) => match (&x.lhs, &x.rhs) {
(Self::StringConstant(_), Self::StringConstant(_))
| (Self::CharConstant(_, _), Self::StringConstant(_)) => true,
_ => false,
},
_ => false,
}
}
pub fn is_constant(&self) -> bool {
match self {
Self::Expr(x) => x.is_constant(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => true,
Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_)
| Self::FnPointer(_)
| Self::True(_)
| Self::False(_)
| Self::Unit(_) => true,
Self::Array(x, _) => x.iter().all(Self::is_constant),
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant),
Self::In(x, _) => match (&x.lhs, &x.rhs) {
(Self::StringConstant(_), Self::StringConstant(_))
| (Self::CharConstant(_, _), Self::StringConstant(_)) => true,
_ => false,
},
_ => false,
}
}
pub fn is_valid_postfix(&self, token: &Token) -> bool {
match self {
Self::Expr(x) => x.is_valid_postfix(token),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => false,
Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::FnPointer(_)
| Self::In(_, _)
| Self::And(_, _)
| Self::Or(_, _)
| Self::True(_)
| Self::False(_)
| Self::Unit(_) => false,
Self::StringConstant(_)
| Self::Stmt(_, _)
| Self::FnCall(_, _)
| Self::Dot(_, _)
| Self::Index(_, _)
| Self::Array(_, _)
| Self::Map(_, _) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
_ => false,
},
Self::Variable(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
Token::Bang => true,
Token::DoubleColon => true,
_ => false,
},
Self::Property(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
_ => false,
},
Self::Custom(_, _) => false,
}
}
#[cfg(not(feature = "no_object"))]
#[inline]
pub(crate) fn into_property(self) -> Self {
match self {
Self::Variable(x) if x.1.is_none() => {
let ident = x.3;
let getter = make_getter(&ident.name);
let setter = make_setter(&ident.name);
Self::Property(Box::new(((getter, setter), ident.into())))
}
_ => self,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn check_struct_sizes() {
use std::mem::size_of;
assert_eq!(size_of::<crate::Dynamic>(), 16);
assert_eq!(size_of::<Option<crate::Dynamic>>(), 16);
assert_eq!(size_of::<crate::Position>(), 4);
assert_eq!(size_of::<crate::ast::Expr>(), 16);
assert_eq!(size_of::<Option<crate::ast::Expr>>(), 16);
assert_eq!(size_of::<crate::ast::Stmt>(), 32);
assert_eq!(size_of::<Option<crate::ast::Stmt>>(), 32);
assert_eq!(size_of::<crate::Scope>(), 72);
assert_eq!(size_of::<crate::LexError>(), 32);
assert_eq!(size_of::<crate::ParseError>(), 16);
assert_eq!(size_of::<crate::EvalAltResult>(), 64);
}
}