shelly-shell 0.5.2

A Rust based Unix style shell with a typed and structured language syntax.

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),
}


// Persistent script state. Variables follow the caller's runtime scopes; types and
// functions keep the lexical identities and snapshots established during compilation.
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();
        // Keep local overrides while replacing only bindings installed by module imports.
        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>>
    {
        // The compiler stages both registries and publishes only a valid submission.
        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>>
    {
        // Preserve the final value so the loader can decide whether to import.
        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>
    {
        // Method lookup uses only this snapshot; ordinary command lookup may also
        // fall back to definitions published by later top-level submissions.
        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?;
        }
    }

    // Enumerate names without resolving or invoking their bodies.
    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();
    }
}