use std::{ cell::RefCell, collections::{ HashMap, HashSet }, rc::Rc };
use crate::runtime::{ color::TtyColorMode, highlighting::SYNTAX_COLORS };
use crate::language::{ ast::AstTopLevel,
native::{ NativeFunction, NativeVisibility },
bytecode::{ FunctionBlock, FunctionBlockRef, FunctionRef, Instruction },
compiler::{ CompileResult, CompileTarget, compile_ast },
data::{ scoped_variables::{ ScopedValue, ScopedVariables, ValueVisibility },
types::{ TypeDefinition, TypeId, TypeKind, TypeRegistry },
value::Value } };
#[derive(Clone)]
pub struct Alias
{
pub scope: String,
pub name: String,
pub arguments: Vec<String>
}
#[derive(Clone)]
pub(super) enum PreludeBinding
{
Type(TypeId),
Variable(Rc<RefCell<ScopedValue>>),
Function(FunctionRef),
Native(Rc<NativeFunction>),
Alias(Rc<Alias>),
Module(String),
}
pub(super) struct Scope
{
pub variables: ScopedVariables,
pub types: TypeRegistry,
pub aliases: HashMap<String, Rc<Alias>>,
pub modules: HashMap<String, String>,
pub native_functions: HashMap<String, Rc<NativeFunction>>,
pub exports: HashSet<String>,
pub prelude_bindings: HashMap<String, PreludeBinding>,
pub prelude_source: Option<String>,
pub imported_types: HashMap<String, Rc<TypeDefinition>>,
pub imported_methods: HashMap<String, FunctionRef>,
base_function_block: FunctionBlockRef,
current_function_block: Option<FunctionBlockRef>,
}
impl Scope
{
pub fn new(natives: impl IntoIterator<Item = Rc<NativeFunction>>,
scope: String, mut types: TypeRegistry, color_mode: TtyColorMode) -> Self
{
let native_functions: HashMap<_, _> = natives.into_iter()
.filter(|item| item.visibility != NativeVisibility::Hidden)
.map(|item| (item.name.to_string(), item)).collect();
types.qualified_functions.extend(native_functions.keys()
.filter(|name| name.contains("::")).cloned());
let builtins = native_functions.values().map(|item| item.name).collect();
let mut variables = ScopedVariables::new_from_environment();
let history_path = types.intern(TypeKind::Optional(types.native_types["String"].id));
for (name, value, type_id) in [
("$history_path", Value::from_string("~/.shelly_history.jsonl".into()), history_path),
("$history_limit", Value::Integer(10000), types.native_types["Integer"].id),
]
{
let _ = variables.create(name.into(), ScopedValue
{
value,
type_id: Some(type_id),
exported: ValueVisibility::Private,
reference: None,
});
}
let _ = variables.create("$repl_syntax".into(), ScopedValue
{
value: Value::Boolean(true),
type_id: Some(types.native_types["Boolean"].id),
exported: ValueVisibility::Private,
reference: None,
});
for &(_, name, color) in SYNTAX_COLORS
{
let _ = variables.create(name.into(), ScopedValue
{
value: Value::from_string(color.into()),
type_id: Some(types.native_types["String"].id),
exported: ValueVisibility::Private,
reference: None,
});
}
let widget_array = types.intern(TypeKind::Array(types.native_types["WidgetFn"].id));
let _ = variables.create("$widgets".to_string(), ScopedValue
{
value: Value::from_array(Vec::new()),
type_id: Some(widget_array),
exported: ValueVisibility::Private,
reference: None,
});
let widget_color = match color_mode
{
TtyColorMode::Tty256 => "\x1b[38;5;80m",
TtyColorMode::TtyBasic => "\x1b[36m",
_ => "\x1b[38;2;49;216;224m",
};
let _ = variables.create("$widget_color".to_string(), ScopedValue
{
value: Value::from_string(widget_color.to_string()),
type_id: Some(types.native_types["String"].id),
exported: ValueVisibility::Private,
reference: None,
});
let prompt_type = types.native_types["Prompt"].id;
let _ = variables.create("$prompt".to_string(), ScopedValue
{
value: types.default_value(prompt_type).expect("Prompt has a native default"),
type_id: Some(prompt_type),
exported: ValueVisibility::Private,
reference: None,
});
Self
{
variables,
types,
aliases: HashMap::new(),
modules: HashMap::new(),
native_functions,
exports: HashSet::new(),
imported_types: HashMap::new(),
imported_methods: HashMap::new(),
prelude_bindings: HashMap::new(),
prelude_source: None,
base_function_block: Rc::new(RefCell::new(FunctionBlock
{
scope,
parent: None,
function_name: None,
declared_functions: HashSet::new(),
builtins: Rc::new(builtins),
functions: HashMap::new(),
})),
current_function_block: None,
}
}
pub fn checkpoint(&self) -> Self
{
Self
{
variables: self.variables.clone(),
types: self.types.clone(),
aliases: self.aliases.clone(),
modules: self.modules.clone(),
native_functions: self.native_functions.clone(),
exports: self.exports.clone(),
imported_types: self.imported_types.clone(),
imported_methods: self.imported_methods.clone(),
prelude_bindings: self.prelude_bindings.clone(),
prelude_source: self.prelude_source.clone(),
base_function_block: Rc::new(RefCell::new(
self.base_function_block.borrow().clone())),
current_function_block: self.current_function_block.clone(),
}
}
pub fn import_native(&mut self, function: Rc<NativeFunction>)
{
let mut block = self.base_function_block.borrow_mut();
Rc::make_mut(&mut block.builtins).insert(function.name);
self.native_functions.insert(function.name.to_string(), function);
}
pub fn import_function(&mut self, name: String, function: FunctionRef)
{
self.base_function_block.borrow_mut().functions.insert(name, function);
}
pub fn prelude_binding(&self, name: &str) -> Option<PreludeBinding>
{
if name.starts_with('$')
{ return self.variables.global_binding(name).map(PreludeBinding::Variable); }
if let Some(id) = self.types.names.get(name)
{ return Some(PreludeBinding::Type(*id)); }
if let Some(function) = self.native_functions.get(name)
{ return Some(PreludeBinding::Native(function.clone())); }
if let Some(function) = self.base_function(name)
{ return Some(PreludeBinding::Function(function)); }
if let Some(alias) = self.aliases.get(name)
{ return Some(PreludeBinding::Alias(alias.clone())); }
self.modules.get(name).cloned().map(PreludeBinding::Module)
}
pub fn remove_prelude_binding(&mut self, name: &str, binding: PreludeBinding)
{
let removed = match binding
{
PreludeBinding::Type(id) if self.types.names.get(name) == Some(&id) =>
{
self.types.names.remove(name);
self.imported_types.remove(name);
true
},
PreludeBinding::Variable(binding) =>
self.variables.remove_global_binding(name, &binding),
PreludeBinding::Function(function) =>
{
let mut block = self.base_function_block.borrow_mut();
if block.functions.get(name).is_some_and(|item| Rc::ptr_eq(item, &function))
{ block.functions.remove(name); true } else { false }
},
PreludeBinding::Native(function) =>
{
if self.native_functions.get(name)
.is_some_and(|item| Rc::ptr_eq(item, &function))
{
self.native_functions.remove(name);
Rc::make_mut(&mut self.base_function_block.borrow_mut().builtins)
.remove(function.name);
true
}
else { false }
},
PreludeBinding::Alias(alias) =>
{
if self.aliases.get(name).is_some_and(|item| Rc::ptr_eq(item, &alias))
{ self.aliases.remove(name); true } else { false }
},
PreludeBinding::Module(key) if self.modules.get(name) == Some(&key) =>
{ self.modules.remove(name); true },
_ => false,
};
if removed { self.exports.remove(name); }
}
pub fn refresh_methods(&mut self, methods: HashMap<String, FunctionRef>)
{
let mut block = self.base_function_block.borrow_mut();
for (name, previous) in &self.imported_methods
{
if block.functions.get(name).is_some_and(|item| Rc::ptr_eq(item, previous))
{ block.functions.remove(name); }
}
for (name, method) in &methods
{ block.functions.entry(name.clone()).or_insert_with(|| method.clone()); }
self.imported_methods = methods;
}
pub fn compile(&mut self, statements: &mut AstTopLevel) -> CompileResult<Vec<Instruction>>
{
compile_ast(&mut self.types, &self.base_function_block, &self.variables,
statements, CompileTarget::Toplevel)
}
pub fn compile_condition(&mut self, statements: &mut AstTopLevel)
-> CompileResult<Vec<Instruction>>
{
compile_ast(&mut self.types, &self.base_function_block, &self.variables,
statements, CompileTarget::Function)
}
pub fn base_function(&self, name: &str) -> Option<FunctionRef>
{
self.base_function_block.borrow().functions.get(name).cloned()
}
pub fn function(&self, name: &str) -> Option<FunctionRef>
{
self.lexical_function(name).or_else(|| self.base_function(name))
}
pub fn lexical_function(&self, name: &str) -> Option<FunctionRef>
{
let mut block = self.current_function_block.as_ref()
.unwrap_or(&self.base_function_block).clone();
loop
{
let parent =
{
let block = block.borrow();
if let Some(function) = block.functions.get(name)
{ return Some(function.clone()); }
block.parent.clone()
};
block = parent?;
}
}
pub fn function_names(&self) -> HashSet<String>
{
let mut names: HashSet<String> = self.base_function_block.borrow().functions.keys()
.cloned().collect();
let mut block = self.current_function_block.clone();
while let Some(current) = block
{
let current = current.borrow();
names.extend(current.functions.keys().cloned());
block = current.parent.clone();
}
names
}
pub fn in_function(&self) -> bool
{
self.current_function_block.is_some()
}
pub fn enter_function(&mut self, functions: FunctionBlockRef) -> Option<FunctionBlockRef>
{
self.variables.push_scope();
self.current_function_block.replace(functions)
}
pub fn exit_function(&mut self, caller: Option<FunctionBlockRef>)
{
self.current_function_block = caller;
self.variables.pop_scope();
}
}