shelly-shell 0.1.0

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

use std::{ cell::RefCell, collections::HashMap, fmt::{ self, Display, Formatter }, rc::Rc };


use crate::language::{ ast::*,
                       bytecode::{ Code, Instruction, Function, FunctionBlock, FunctionBlockRef },
                       data::value::Value,
                       text::location::Location,
                       parser::ParserError };


pub enum ErrorWhat
{
    ParserError(ParserError),
}


pub struct CompileError
{
    pub location: Option<Location>,
    pub what: ErrorWhat
}


impl Display for CompileError
{
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result
    {
        match &self.what
        {
            ErrorWhat::ParserError(error) =>
                {
                    if let Some(location) = &self.location
                    {
                        write!(f, "Parser error: {}: {}", location, error)
                    }
                    else
                    {
                        write!(f, "Parser error: {}", error)
                    }
                }
        }
    }
}


impl From<ParserError> for CompileError
{
    fn from(error: ParserError) -> Self
    {
        CompileError
        {
            location: None,
            what: ErrorWhat::ParserError(error)
        }
    }
}


pub type CompileResult<T> = Result<T, CompileError>;


fn compile_expression(instructions: &mut Vec<Instruction>, expression: &AstExpression)
{
    match &expression.kind
    {
        AstExpressionKind::Symbol(symbol) =>
            {
                // If the string contains a * then we need to handle it as a file glob.
                if symbol.name.contains('*')
                {
                    instructions.push(Instruction
                        {
                            location: Some(expression.location.clone()),
                            code: Code::GlobFiles,
                            operand: Some(Value::String(symbol.name.clone()))
                        });
                }
                else
                {
                    instructions.push(Instruction
                        {
                            location: Some(expression.location.clone()),
                            code: Code::Push,
                            operand: Some(Value::String(symbol.name.clone()))
                        });
                }
            },

        AstExpressionKind::Literal(value) =>
            {
                instructions.push(Instruction
                    {
                        location: Some(expression.location.clone()),
                        code: Code::Push,
                        operand: Some(value.value.clone())
                    });
            },

        AstExpressionKind::Variable(variable) =>
            {
                instructions.push(Instruction
                    {
                        location: Some(expression.location.clone()),
                        code: Code::GetVariable,
                        operand: Some(Value::String(variable.name.clone()))
                    });
            }

        AstExpressionKind::VariableSplat(variable) =>
            {
                instructions.push(Instruction
                        {
                            location: Some(expression.location.clone()),
                            code: Code::GetVariable,
                            operand: Some(Value::String(variable.name.clone()))
                        });

                    instructions.push(Instruction
                        {
                            location: Some(expression.location.clone()),
                            code: Code::ExpandArray,
                            operand: None
                        });
            },

        AstExpressionKind::MathExpression(operator, lhs, rhs) =>
            {
                compile_expression(instructions, lhs);
                compile_expression(instructions, rhs);

                instructions.push(Instruction
                    {
                        location: Some(expression.location.clone()),
                        code: match operator
                            {
                                AstMathOperator::Add      => Code::MathAdd,
                                AstMathOperator::Subtract => Code::MathSubtract,
                                AstMathOperator::Multiply => Code::MathMultiply,
                                AstMathOperator::Divide   => Code::MathDivide,
                                AstMathOperator::Modulo   => Code::MathModulo,
                            },
                        operand: None
                    });
            }

    }

    if let Some(string_flag) = &expression.string_flag
    {
        if let AstStringFlag::Interpolated = string_flag
        {
            instructions.push(Instruction
                {
                    location: Some(expression.location.clone()),
                    code: Code::InterpolateString,
                    operand: None
                });
        }
    }

    // Only an unquoted source word can request home expansion. Variable values and quoted literals
    // retain their spelling. GlobFiles expands its own prefix.
    if    let AstExpressionKind::Symbol(symbol) = &expression.kind
       && (symbol.name == "~" || symbol.name.starts_with("~/"))
       && !symbol.name.contains('*')
    {
        instructions.push(Instruction
            {
                location: Some(expression.location.clone()),
                code: Code::ExpandPath,
                operand: None
            });
    }
}


fn compile_let_statement(instructions: &mut Vec<Instruction>, let_statement: &AstLetStatement)
{
    instructions.push(Instruction
        {
            location: Some(let_statement.location.clone()),
            code: Code::NewVariable,
            operand: Some(Value::String(let_statement.identifier.clone()))
        });

    if let_statement.export_flag == AstExportFlag::Exported
    {
        instructions.push(Instruction
            {
                location: None,
                code: Code::ExportVariable,
                operand: Some(Value::String(let_statement.identifier.clone()))
            });
    }

    compile_expression(instructions, &let_statement.expression);

    instructions.push(Instruction
        {
            location: Some(let_statement.location.clone()),
            code: Code::SetVariable,
            operand: Some(Value::String(let_statement.identifier.clone()))
        });
}

fn compile_set_statement(instructions: &mut Vec<Instruction>, set_statement: &AstSetStatement)
{
    compile_expression(instructions, &set_statement.expression);

    instructions.push(Instruction
        {
            location: Some(set_statement.location.clone()),
            code: Code::SetVariable,
            operand: Some(Value::String(set_statement.identifier.clone()))
        });
}


fn compile_alias_statement(instructions: &mut Vec<Instruction>, alias_statement: &AstAliasStatement)
{
    // Alias targets are unquoted symbols. Arguments retain their quoting and
    // otherwise remain stored values, without interpolation or glob expansion.
    instructions.push(Instruction
        {
            location: Some(alias_statement.location.clone()),
            code: Code::Push,
            operand: Some(Value::String(alias_statement.target.clone()))
        });
    instructions.push(Instruction
        {
            location: Some(alias_statement.location.clone()),
            code: Code::ExpandPath,
            operand: None
        });

    for argument in &alias_statement.arguments
    {
        instructions.push(Instruction
            {
                location: Some(alias_statement.location.clone()),
                code: Code::Push,
                operand: Some(argument.value.clone())
            });
        if argument.expand_path
        {
            instructions.push(Instruction
                {
                    location: Some(alias_statement.location.clone()),
                    code: Code::ExpandPath,
                    operand: None
                });
        }
    }

    instructions.push(Instruction
        {
            location: Some(alias_statement.location.clone()),
            code: Code::NewAlias,
            operand: Some(Value::Array(vec![Value::String(alias_statement.alias.clone()),
                                           Value::Integer(alias_statement.arguments.len() as i64)]))
        });
}


fn compile_execute_statement(instructions: &mut Vec<Instruction>,
                             execute_statement: &AstExecuteStatement)
{
    instructions.push(Instruction
        {
            location: None,
            code: Code::Push,
            operand: Some(Value::String(execute_statement.executable_name.clone()))
        });

    if execute_statement.expand_path
    {
        instructions.push(Instruction
            {
                location: Some(execute_statement.location.clone()),
                code: Code::ExpandPath,
                operand: None
            });
    }

    for argument in &execute_statement.arguments
    {
        compile_expression(instructions, argument);
    }

    instructions.push(Instruction
        {
            location: Some(execute_statement.location.clone()),
            code: Code::Execute,
            operand: Some(Value::Integer(execute_statement.arguments.len() as i64))
        });
}


fn compile_function_definition(parent_block: &FunctionBlockRef,
                               function_statement: &AstFunctionStatement) -> CompileResult<()>
{
    let function_block = Rc::new(RefCell::new(FunctionBlock
        {
            parent: Some(parent_block.clone()),
            functions: HashMap::new()
        }));

    let instructions = compile_ast(&function_block, &function_statement.body)?;

    let new_function = Rc::new(Function
        {
            functions: function_block,
            arguments: function_statement.parameters.clone(),
            code: instructions
        });

    parent_block.borrow_mut().functions.insert(function_statement.name.clone(), new_function);

    Ok(())
}


pub fn compile_ast(function_block: &FunctionBlockRef,
                   ast: &AstTopLevel) -> CompileResult<Vec<Instruction>>
{
    let mut instructions = Vec::new();

    for ast_item in ast
    {
        match ast_item
        {
            AstStatement::NullStatement => {},

            AstStatement::LetStatement(let_statement) =>
                {
                    compile_let_statement(&mut instructions, let_statement);
                },

            AstStatement::SetStatement(set_statement) =>
                {
                    compile_set_statement(&mut instructions, set_statement);
                },

            AstStatement::AliasStatement(alias_statement) =>
                {
                    compile_alias_statement(&mut instructions, alias_statement);
                },

            AstStatement::ExecuteStatement(execute_statement) =>
                {
                    compile_execute_statement(&mut instructions, execute_statement);
                }

            AstStatement::FunctionDefinition(function_statement) =>
                {
                    compile_function_definition(&function_block, function_statement)?;
                }
        }
    }

    Ok(instructions)
}