use crate::types::{Value, GrugEntity, FileId};
use crate::ast::{
Parameter, Statement, Expr, ExprData, MemberVariable, OnFunction,
HelperFunction, UnaryOperator, BinaryOperator, Type, GrugAst,
};
use crate::xar::{Xar, XarHandle};
use crate::arena::Arena;
use crate::backend::Backend;
use crate::ntstring::{NTStrPtr};
use gruggers_core::runtime_error::{RuntimeError, ON_FN_TIME_LIMIT, MAX_RECURSION_LIMIT};
use gruggers_core::state::State;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::time::{Instant, Duration};
use allocator_api2::boxed::Box;
use allocator_api2::vec::Vec;
fn copy_into_arena<'arena>(ast: &GrugAst<'_>, arena: &'arena Arena) -> GrugAst<'arena> {
let mut members = Vec::with_capacity_in(ast.members.len(), arena);
for member in ast.members.iter() {
let name = copy_string(member.name, arena);
let ty = copy_type(member.ty, arena);
let assignment_expr = copy_expr(&member.assignment_expr, arena);
members.push(MemberVariable {
name,
ty,
type_span: member.type_span,
assignment_expr,
span: member.span,
});
}
let mut on_functions = Vec::with_capacity_in(ast.on_functions.len(), arena);
for on_function in ast.on_functions.iter() {
let Some(on_function) = on_function else {on_functions.push(None); continue;};
let name = copy_string(on_function.name, arena);
let mut parameters = Vec::with_capacity_in(on_function.parameters.len(), arena);
for parameter in on_function.parameters {
parameters.push(Parameter {
name: copy_string(parameter.name, arena),
ty: copy_type(parameter.ty, arena),
..*parameter
});
}
let body_statements = copy_statements(on_function.body_statements, arena);
on_functions.push(Some(&*Box::leak(Box::new_in(OnFunction{
name,
parameters: parameters.leak(),
body_statements,
span: on_function.span,
}, arena))));
}
let mut helper_functions = Vec::with_capacity_in(ast.helper_functions.len(), arena);
for helper_function in ast.helper_functions.iter() {
let name = copy_string(helper_function.name, arena);
let return_type = copy_type(helper_function.return_type, arena);
let mut parameters = Vec::with_capacity_in(helper_function.parameters.len(), arena);
for parameter in helper_function.parameters {
parameters.push(Parameter {
name: copy_string(parameter.name, arena),
ty: copy_type(parameter.ty, arena),
..*parameter
});
}
let body_statements = copy_statements(helper_function.body_statements, arena);
helper_functions.push(HelperFunction{
name,
return_type,
return_type_span: helper_function.return_type_span,
parameters: parameters.leak(),
body_statements,
span: helper_function.span
});
}
let file_text = arena.copy_str_into_nt(ast.file_text.to_str());
GrugAst {
members: members.leak(),
on_functions: on_functions.leak(),
helper_functions: helper_functions.leak(),
file_text: file_text.as_ntstrptr(),
}
}
fn copy_statements<'arena>(stmts: &[Statement<'_>], arena: &'arena Arena) -> &'arena mut [Statement<'arena>] {
let mut vec = Vec::with_capacity_in(stmts.len(), arena);
for stmt in stmts {
let stmt = match stmt {
Statement::Variable {
name,
ty,
type_span,
assignment_expr,
name_span,
} => Statement::Variable {
name: copy_string(*name, arena),
ty: ty.map(|ty| &*Box::leak(Box::new_in(copy_type(*ty, arena), arena))),
type_span: *type_span,
assignment_expr : copy_expr(assignment_expr, arena),
name_span: *name_span,
},
Statement::Call(expr) => Statement::Call(copy_expr(expr, arena)),
Statement::If {
condition,
is_chained,
if_block,
else_block,
} => {
let mut ifs = Vec::new();
let mut condition = condition;
let mut is_chained = is_chained;
let mut if_block = if_block;
let mut else_block = else_block;
while *is_chained {
ifs.push((
copy_expr(condition, arena),
*is_chained,
copy_statements(if_block, arena),
));
(condition, is_chained, if_block, else_block) = match else_block {
[Statement::If{condition, is_chained, if_block, else_block}] => (condition, is_chained, if_block, else_block),
_ => panic!("invalid ast"),
};
}
let mut current = Statement::If {
condition: copy_expr(condition, arena),
is_chained: *is_chained,
if_block: copy_statements(if_block, arena),
else_block: copy_statements(else_block, arena),
};
for (condition, is_chained, if_block) in ifs.into_iter().rev() {
current = Statement::If {
condition,
is_chained,
if_block,
else_block: std::slice::from_mut(Box::leak(Box::new_in(current, arena))),
};
}
current
}
Statement::While {
condition,
block,
} => Statement::While {
condition: copy_expr(condition, arena),
block: copy_statements(block, arena),
},
Statement::Return {
return_span,
expr,
} => Statement::Return {
return_span: *return_span,
expr: expr.as_ref().map(|expr| Box::leak(Box::new_in(copy_expr(expr, arena), arena))),
},
Statement::Comment{comment_span, value} => Statement::Comment{comment_span: *comment_span, value: copy_string(*value, arena)},
Statement::Break(span) => Statement::Break(*span),
Statement::Continue(span) => Statement::Continue(*span),
Statement::EmptyLine => Statement::EmptyLine,
};
vec.push(stmt);
}
vec.leak()
}
fn copy_expr<'arena>(expr: &Expr<'_>, arena: &'arena Arena) -> Expr<'arena> {
let result_type = expr.result_type.map(|res| &*Box::leak(Box::new_in(copy_type(*res, arena), arena)));
let data = match &expr.data {
ExprData::True => ExprData::True,
ExprData::False => ExprData::False,
ExprData::String(string) => ExprData::String(copy_string(*string, arena)),
ExprData::Resource(string) => ExprData::Resource(copy_string(*string, arena)),
ExprData::Entity(string) => ExprData::Entity(copy_string(*string, arena)),
ExprData::Identifier(string) => ExprData::Identifier(copy_string(*string, arena)),
ExprData::Number(number, string) => ExprData::Number(*number, copy_string(*string, arena)),
ExprData::Unary {
op,
expr,
op_span,
} => {
ExprData::Unary {
op: *op,
expr: Box::leak(Box::new_in(copy_expr(expr, arena), arena)),
op_span: *op_span,
}
},
ExprData::Binary {
op,
left,
right,
op_span,
} => {
ExprData::Binary {
op: *op,
left: Box::leak(Box::new_in(copy_expr(left, arena), arena)),
right: Box::leak(Box::new_in(copy_expr(right, arena), arena)),
op_span: *op_span,
}
},
ExprData::Call {
receiver,
name,
args,
ptr,
name_span,
generics,
} => {
ExprData::Call {
receiver: receiver.as_ref().map(|x| &mut *arena.alloc_into(copy_expr(x, arena))),
name: copy_string(*name, arena),
args: arena.slice_from_iter(args.iter().map(|expr| copy_expr(expr, arena))),
ptr: *ptr,
name_span: *name_span,
generics
}
},
ExprData::Parenthesized(expr) => ExprData::Parenthesized(Box::leak(Box::new_in(copy_expr(expr, arena), arena))),
};
Expr {
result_type,
data,
span: expr.span,
}
}
fn copy_type<'arena>(ty: Type<'_>, arena: &'arena Arena) -> Type<'arena> {
match ty {
Type::Void => Type::Void,
Type::Bool => Type::Bool,
Type::Number => Type::Number,
Type::String => Type::String,
Type::Entity{entity_type: None} => Type::Entity{entity_type: None},
Type::Resource{extension} => Type::Resource{extension: copy_string(extension, arena)},
Type::Id{name, generics} => Type::Id{
name: copy_string(name, arena),
generics: {
let mut temp = Vec::with_capacity_in(generics.len(), arena);
temp.extend(generics.iter().map(|ty| {
copy_type(*ty, arena)
}));
temp.leak()
}
},
Type::Entity{entity_type: Some(entity_type)} => Type::Entity{entity_type: Some(copy_string(entity_type, arena))},
Type::Existential{..} => panic!("Existential passed to backend"),
}
}
fn copy_string<'arena>(string: NTStrPtr<'_>, arena: &'arena Arena) -> NTStrPtr<'arena> {
arena.copy_str_into_nt(string.to_str()).as_ntstrptr()
}
struct GrugEntityData {
pub(crate) global_variables: HashMap<&'static str, Cell<Value>>,
}
impl GrugEntityData {
pub(crate) fn get_global_variable(&self, name: &str) -> Option<&Cell<Value>> {
self.global_variables.get(name)
}
}
struct CompiledFile {
file: GrugAst<'static>,
data: Xar<GrugEntityData>,
_arena: Arena,
}
impl CompiledFile {
fn new(file: GrugAst) -> Self {
let arena = Arena::new();
let file = unsafe{std::mem::transmute::<GrugAst<'_>, GrugAst<'static>>(copy_into_arena(&file, &arena))};
Self {
file,
data: Xar::new(),
_arena: arena,
}
}
}
pub struct Interpreter {
files: RefCell<Vec<CompiledFile>>,
}
struct CallStack {
start_time: Instant,
local_variables: Vec<Vec<HashMap<&'static str, Value>>>,
}
impl CallStack {
fn new() -> Self {
Self {
start_time: Instant::now(),
local_variables: Vec::new(),
}
}
fn pop_scope(&mut self) {
self.local_variables.last_mut()
.expect("must already have a stack frame").pop()
.expect("must have scope");
}
fn add_local_variable(&mut self, name: &str, value: Value) {
assert!(self.local_variables.last_mut()
.expect("must have stack frame").last_mut()
.expect("last frame must have scope").insert(unsafe{std::mem::transmute::<&str, &'static str>(name)}, value)
.is_none(), "variable already exists");
}
fn pop_stack_frame(&mut self) {
self.local_variables.pop().expect("must have stack frame");
}
fn push_scope(&mut self) {
self.local_variables.last_mut()
.expect("must already have a stack frame")
.push(HashMap::new());
}
fn push_stack_frame(&mut self) {
self.local_variables.push(Vec::new());
}
fn get_local_variable(&mut self, name: &str) -> Option<&mut Value> {
for scope in self.local_variables.last_mut()?{
if let Some(val) = scope.get_mut(name) {
return Some(val)
}
}
None
}
}
enum GrugControlFlow {
Return(Value),
Break,
Continue,
None,
}
impl Interpreter {
pub fn new() -> Self {
Self {
files: RefCell::new(Vec::new()),
}
}
#[expect(clippy::too_many_arguments)]
fn run_function<GrugState: State>(&self, call_stack: &mut CallStack, state: &GrugState, file: &CompiledFile, entity: &GrugEntityData, arguments: &'static [Parameter], values: &[Value], statements: &[Statement]) -> Option<Value> {
if call_stack.local_variables.len() > MAX_RECURSION_LIMIT {
state.handle_runtime_error(RuntimeError::StackOverflow);
return None
}
if arguments.len() != values.len() {
panic!("argument count mismatch")
}
call_stack.push_stack_frame();
call_stack.push_scope();
for (argument, value) in arguments.iter().zip(values) {
call_stack.add_local_variable(argument.name.to_str(), *value);
}
let value = self.run_statements(call_stack, state, file, entity, statements)?;
let value = match value {
GrugControlFlow::Return(value) => value,
GrugControlFlow::None => Value{void: ()},
GrugControlFlow::Break => unreachable!(),
GrugControlFlow::Continue => unreachable!(),
};
call_stack.pop_scope();
call_stack.pop_stack_frame();
Some(value)
}
fn run_statements<GrugState: State>(&self, call_stack: &mut CallStack, state: &GrugState, file: &CompiledFile, entity: &GrugEntityData, statements: &[Statement]) -> Option<GrugControlFlow> {
call_stack.push_scope();
let mut ret_val = GrugControlFlow::None;
'outer: for statement in statements {
match statement {
Statement::Variable{
name,
ty,
type_span: _,
assignment_expr,
name_span: _,
} => {
let name = name.to_str();
let assignment_expr = self.run_expr(call_stack, state, file, entity, assignment_expr)?;
if ty.is_some() {
call_stack.add_local_variable(name, assignment_expr);
} else if let Some(var) = call_stack.get_local_variable(name) {
*var = assignment_expr;
} else if let Some(var) = entity.get_global_variable(name) {
var.set(assignment_expr);
} else {
panic!("variable not found");
}
},
Statement::Call(expr) => {
self.run_expr(call_stack, state, file, entity, expr)?;
},
Statement::If{
condition,
is_chained,
if_block,
else_block,
} => {
let mut condition = condition;
let mut is_chained = is_chained;
let mut if_block = if_block;
let mut else_block = else_block;
loop {
if unsafe{self.run_expr(call_stack, state, file, entity, condition)?.bool} != 0 {
let control_flow = self.run_statements(call_stack, state, file, entity, if_block)?;
if let GrugControlFlow::None = control_flow {
break;
} else {
ret_val = control_flow;
break 'outer;
}
} else {
if *is_chained {
(condition, is_chained, if_block, else_block) = match else_block {
[Statement::If{condition, is_chained, if_block, else_block}] => (condition, is_chained, if_block, else_block),
_ => panic!("invalid ast"),
};
continue;
} else {
let control_flow = self.run_statements(call_stack, state, file, entity, else_block)?;
if let GrugControlFlow::None = control_flow {
break;
} else {
ret_val = control_flow;
break 'outer;
}
}
}
}
},
Statement::Return{
return_span: _,
expr,
} => {
if let Some(expr) = expr {
ret_val = GrugControlFlow::Return(self.run_expr(call_stack, state, file, entity, expr)?);
} else {
ret_val = GrugControlFlow::Return(Value{void: ()});
}
break 'outer;
},
Statement::While{
condition,
block,
} => {
loop {
let condition = unsafe{self.run_expr(call_stack, state, file, entity, condition)?.bool};
if condition == 0 {
break;
}
match self.run_statements(call_stack, state, file, entity, block)? {
GrugControlFlow::Return(value) => {
ret_val = GrugControlFlow::Return(value);
break 'outer;
}
GrugControlFlow::Continue => (),
GrugControlFlow::Break => break,
GrugControlFlow::None => (),
}
}
},
Statement::Comment{comment_span: _, value: _} => (),
Statement::Break(_) => {
ret_val = GrugControlFlow::Break;
break 'outer;
},
Statement::Continue(_) => {
ret_val = GrugControlFlow::Continue;
break 'outer;
},
Statement::EmptyLine => (),
}
}
call_stack.pop_scope();
Some(ret_val)
}
fn run_expr<GrugState: State>(&self, call_stack: &mut CallStack, state: &GrugState, file: &CompiledFile, entity: &GrugEntityData, expr: &Expr) -> Option<Value> {
if call_stack.start_time.elapsed() > Duration::from_millis(ON_FN_TIME_LIMIT) {
state.set_runtime_error(RuntimeError::ExceededTimeLimit);
return None;
}
Some(match &expr.data {
ExprData::True => Value{bool: 1},
ExprData::False => Value{bool: 0},
ExprData::String(value) => Value{string: unsafe{std::mem::transmute::<NTStrPtr, NTStrPtr<'static>>(*value)}},
ExprData::Resource(value) => Value{string: unsafe{std::mem::transmute::<NTStrPtr, NTStrPtr<'static>>(*value)}},
ExprData::Entity(value) => Value{string: unsafe{std::mem::transmute::<NTStrPtr, NTStrPtr<'static>>(*value)}},
ExprData::Number (value, _) => Value{number: *value},
ExprData::Identifier(name) => {
let name = name.to_str();
if let Some(var) = call_stack.get_local_variable(name) {
*var
} else {
entity.get_global_variable(name)
.expect("could not find variable")
.get()
}
},
ExprData::Unary{
op,
expr,
..
} => {
let mut value = self.run_expr(call_stack, state, file, entity, expr)?;
match (op, &expr.result_type) {
(UnaryOperator::Not, Some(Type::Bool)) => unsafe{value.bool = (value.bool == 0) as u8},
(UnaryOperator::Minus, Some(Type::Number)) => unsafe{value.number = -value.number},
_ => unreachable!(),
}
value
}
ExprData::Binary{
op,
left,
right,
..
} => {
let first_value = self.run_expr(call_stack, state, file, entity, left)?;
let mut second_value = || self.run_expr(call_stack, state, file, entity, right);
match (op, &left.result_type) {
(BinaryOperator::Or, Some(Type::Bool )) => Value{bool: unsafe{first_value.bool | second_value()?.bool}},
(BinaryOperator::And, Some(Type::Bool )) => Value{bool: unsafe{(first_value.bool != 0 && second_value()?.bool != 0) as u8}},
(BinaryOperator::DoubleEquals, Some(ty) ) => {
let value = match ty {
Type::Bool => !unsafe{(first_value.bool == 0) ^ (second_value()?.bool == 0)},
Type::Number => unsafe{first_value.number == second_value()?.number},
Type::Id{..} => unsafe{first_value.id == second_value()?.id},
Type::String => {
unsafe{first_value.string.to_str() == second_value()?.string.to_str()}
},
_ => unreachable!(),
};
Value{bool: value as u8}
},
(BinaryOperator::NotEquals, Some(ty) ) => {
let value = match ty {
Type::Bool => unsafe{(first_value.bool == 0) ^ (second_value()?.bool == 0)}
Type::Number => unsafe{first_value.number != second_value()?.number}
Type::Id{..} => unsafe{first_value.id != second_value()?.id}
Type::String => {
unsafe{first_value.string.to_str() != second_value()?.string.to_str()}
}
_ => unreachable!(),
};
Value{bool: value as u8}
},
(BinaryOperator::Greater, Some(Type::Number)) => Value{bool: unsafe{first_value.number > second_value()?.number} as u8},
(BinaryOperator::GreaterEquals, Some(Type::Number)) => Value{bool: unsafe{first_value.number >= second_value()?.number} as u8},
(BinaryOperator::Less, Some(Type::Number)) => Value{bool: unsafe{first_value.number < second_value()?.number} as u8},
(BinaryOperator::LessEquals, Some(Type::Number)) => Value{bool: unsafe{first_value.number <= second_value()?.number} as u8},
(BinaryOperator::Plus, Some(Type::Number)) => Value{number: unsafe{first_value.number + second_value()?.number}},
(BinaryOperator::Minus, Some(Type::Number)) => Value{number: unsafe{first_value.number - second_value()?.number}},
(BinaryOperator::Multiply, Some(Type::Number)) => Value{number: unsafe{first_value.number * second_value()?.number}},
(BinaryOperator::Division, Some(Type::Number)) => Value{number: unsafe{first_value.number / second_value()?.number}},
_ => unreachable!(),
}
}
ExprData::Call{
name,
args,
ptr: None,
..
} => {
let name = name.to_str();
let values = args.iter().map(|argument| self.run_expr(call_stack, state, file, entity, argument)).collect::<Option<Vec<_>>>()?;
for helper_fn in file.file.helper_functions.iter() {
if helper_fn.name.to_str() != name {
continue;
}
return self.run_function(call_stack, state, file, entity, helper_fn.parameters, &values, &*helper_fn.body_statements);
}
unreachable!("helper function not found");
}
ExprData::Call{
receiver,
name: _,
args,
ptr: Some(ptr),
..
} => {
let mut values = if let Some(receiver) = receiver {
vec![self.run_expr(call_stack, state, file, entity, receiver)?]
} else {
vec![]
};
args.iter().map(|arg| Some(values.push(self.run_expr(call_stack, state, file, entity, arg)?))).collect::<Option<Vec<()>>>()?;
let ret_val = unsafe{ptr(state as *const _ as _, values.as_ptr(), &[] as *const _)};
let ret_val = if expr.result_type == Some(&Type::Void) {Value{void: ()}} else {ret_val};
if state.is_errorring() {
return None;
}
ret_val
}
ExprData::Parenthesized(expr) => {
self.run_expr(call_stack, state, file, entity, expr)?
}
})
}
fn init_global_variables<GrugState: State>(&self, state: &GrugState, file: &CompiledFile, entity: &mut GrugEntityData) -> Option<()> {
file.file.members.iter().map(|variable| {
let value = self.run_expr(
&mut CallStack::new(),
state,
file,
entity,
&variable.assignment_expr
)?;
entity.global_variables.insert(variable.name.to_str(), Cell::new(value));
Some(())
}).collect::<Option<Vec<_>>>()?;
Some(())
}
}
impl Default for Interpreter {
fn default() -> Self {
Self::new()
}
}
impl Backend for Interpreter {
#[inline]
fn insert_file(&self, id: FileId, file: GrugAst) {
let compiled_file = CompiledFile::new(file);
let mut files = self.files.borrow_mut();
if let Some(old_file) = files.get_mut(id.0 as usize) {
*old_file = compiled_file;
} else if files.len() == id.0 as usize {
files.push(compiled_file);
} else {
unreachable!("GrugScriptIds must be contigious, Expected {}, got {}", files.len(), id.0);
}
}
#[inline]
fn init_entity<GrugState: State>(&self, state: &GrugState, entity: &GrugEntity) -> bool {
let file = self.files.borrow();
let file = file.get(entity.file_id.0 as usize)
.expect("file already compiled");
let mut data = GrugEntityData {
global_variables: HashMap::from([("me", Cell::new(Value{id:entity.id}))]),
};
if self.init_global_variables(state, file, &mut data).is_none() {
return false;
}
let data = file.data.insert(data);
entity.members.set(data.as_ptr().cast());
true
}
#[inline]
fn clear_entities(&mut self) {
self.files.borrow_mut().iter_mut().for_each(|file| {
file.data.clear();
});
}
#[inline]
unsafe fn destroy_entity_data(&self, entity: &GrugEntity) {
let file = self.files.borrow();
let file = file.get(entity.file_id.0 as usize)
.expect("file compiled");
let data_ptr = unsafe{XarHandle::from_ptr(entity.members.get().cast::<GrugEntityData>())};
debug_assert!(file.data.contains(data_ptr));
unsafe {file.data.delete(data_ptr)};
}
#[inline]
unsafe fn call_on_function_raw<GrugState: State>(&self, state: &GrugState, entity: &GrugEntity, on_fn_index: usize, values: *const Value) -> bool {
let file = &self.files.borrow();
let file = file.get(entity.file_id.0 as usize)
.expect("file already created");
let Some(on_function) = &file.file.on_functions[on_fn_index] else {
return false;
};
let values = if on_function.parameters.is_empty() {
&[]
} else {
unsafe{std::slice::from_raw_parts(values, on_function.parameters.len())}
};
self.run_function(
&mut CallStack::new(),
state,
file,
unsafe{entity.members.get().cast::<GrugEntityData>().as_ref()},
on_function.parameters,
values,
on_function.body_statements
).is_some()
}
#[inline]
fn call_on_function<GrugState: State>(&self, state: &GrugState, entity: &GrugEntity, on_fn_index: usize, values: &[Value]) -> bool {
let file = &self.files.borrow();
let file = file.get(entity.file_id.0 as usize)
.expect("file already created");
let Some(on_function) = &file.file.on_functions[on_fn_index] else {
return false;
};
self.run_function(
&mut CallStack::new(),
state,
file,
unsafe{entity.members.get().cast::<GrugEntityData>().as_ref()},
on_function.parameters,
values,
on_function.body_statements
).is_some()
}
#[inline]
fn raise_runtime_error<GrugState: State>(&self, state: &GrugState, message: &str) {
todo!();
}
}