use self::syntax::OptionSpec;
use crate::common::{output, report_error, report_failure, to_single_message};
use thiserror::Error;
use yash_env::function::Function;
use yash_env::option::State;
use yash_env::semantics::Field;
use yash_env::variable::{Value, Variable};
use yash_env::Env;
use yash_syntax::source::pretty::{Annotation, AnnotationType, MessageBase};
use yash_syntax::source::Location;
mod print_functions;
mod print_variables;
mod set_functions;
mod set_variables;
pub mod syntax;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum VariableAttr {
ReadOnly,
Export,
}
impl VariableAttr {
#[must_use]
pub fn test(&self, var: &Variable) -> State {
let is_on = match self {
VariableAttr::ReadOnly => var.is_read_only(),
VariableAttr::Export => var.is_exported,
};
State::from(is_on)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Scope {
Global,
Local,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SetVariables {
pub variables: Vec<Field>,
pub attrs: Vec<(VariableAttr, State)>,
pub scope: Scope,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintVariables {
pub variables: Vec<Field>,
pub attrs: Vec<(VariableAttr, State)>,
pub scope: Scope,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FunctionAttr {
ReadOnly,
}
impl FunctionAttr {
#[must_use]
fn test(&self, function: &Function) -> State {
let is_on = match self {
Self::ReadOnly => function.is_read_only(),
};
State::from(is_on)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SetFunctions {
pub functions: Vec<Field>,
pub attrs: Vec<(FunctionAttr, State)>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintFunctions {
pub functions: Vec<Field>,
pub attrs: Vec<(FunctionAttr, State)>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintContext<'a> {
pub builtin_name: &'a str,
pub builtin_is_significant: bool,
pub options_allowed: &'a [OptionSpec<'a>],
}
pub const PRINT_CONTEXT: PrintContext<'static> = PrintContext {
builtin_name: "typeset",
builtin_is_significant: false,
options_allowed: self::syntax::ALL_OPTIONS,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Command {
SetVariables(SetVariables),
PrintVariables(PrintVariables),
SetFunctions(SetFunctions),
PrintFunctions(PrintFunctions),
}
impl From<SetVariables> for Command {
fn from(v: SetVariables) -> Self {
Self::SetVariables(v)
}
}
impl From<PrintVariables> for Command {
fn from(v: PrintVariables) -> Self {
Self::PrintVariables(v)
}
}
impl From<SetFunctions> for Command {
fn from(v: SetFunctions) -> Self {
Self::SetFunctions(v)
}
}
impl From<PrintFunctions> for Command {
fn from(v: PrintFunctions) -> Self {
Self::PrintFunctions(v)
}
}
impl Command {
pub fn execute(
self,
env: &mut Env,
print_context: &PrintContext,
) -> Result<String, Vec<ExecuteError>> {
match self {
Self::SetVariables(command) => command.execute(env),
Self::PrintVariables(command) => command.execute(&env.variables, print_context),
Self::SetFunctions(command) => command.execute(&mut env.functions),
Self::PrintFunctions(command) => command.execute(&env.functions, print_context),
}
}
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot assign to read-only variable {name:?}")]
pub struct AssignReadOnlyError {
pub name: String,
pub new_value: Value,
pub assigned_location: Location,
pub read_only_location: Location,
}
impl From<AssignReadOnlyError> for yash_env::variable::AssignError {
fn from(e: AssignReadOnlyError) -> Self {
Self {
new_value: e.new_value,
assigned_location: Some(e.assigned_location),
read_only_location: e.read_only_location,
}
}
}
#[cfg(feature = "yash-semantics")]
impl From<AssignReadOnlyError> for yash_semantics::expansion::AssignReadOnlyError {
fn from(e: AssignReadOnlyError) -> Self {
Self {
name: e.name,
new_value: e.new_value,
read_only_location: e.read_only_location,
}
}
}
impl MessageBase for AssignReadOnlyError {
fn message_title(&self) -> std::borrow::Cow<str> {
"cannot assign to read-only variable".into()
}
fn main_annotation(&self) -> Annotation<'_> {
Annotation::new(
AnnotationType::Error,
self.to_string().into(),
&self.assigned_location,
)
}
fn additional_annotations<'a, T: Extend<Annotation<'a>>>(&'a self, results: &mut T) {
results.extend(std::iter::once(Annotation::new(
AnnotationType::Info,
"the variable was made read-only here".into(),
&self.read_only_location,
)))
}
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[error("cannot cancel read-only-ness of {name}")]
pub struct UndoReadOnlyError {
pub name: Field,
pub read_only_location: Location,
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
pub enum ExecuteError {
AssignReadOnlyVariable(#[from] AssignReadOnlyError),
UndoReadOnlyVariable(UndoReadOnlyError),
UndoReadOnlyFunction(UndoReadOnlyError),
ModifyUnsetFunction(Field),
PrintUnsetVariable(Field),
PrintUnsetFunction(Field),
}
impl MessageBase for ExecuteError {
fn message_title(&self) -> std::borrow::Cow<str> {
match self {
Self::AssignReadOnlyVariable(error) => return error.message_title(),
Self::UndoReadOnlyVariable(_) => "cannot cancel read-only-ness of variable",
Self::UndoReadOnlyFunction(_) => "cannot cancel read-only-ness of function",
Self::ModifyUnsetFunction(_) => "cannot modify non-existing function",
Self::PrintUnsetVariable(_) => "cannot print non-existing variable",
Self::PrintUnsetFunction(_) => "cannot print non-existing function",
}
.into()
}
fn main_annotation(&self) -> Annotation<'_> {
let (message, location) = match self {
Self::AssignReadOnlyVariable(error) => return error.main_annotation(),
Self::UndoReadOnlyVariable(error) => (
format!("read-only variable `{}`", error.name),
&error.name.origin,
),
Self::UndoReadOnlyFunction(error) => (
format!("read-only function `{}`", error.name),
&error.name.origin,
),
Self::PrintUnsetVariable(field) => {
(format!("non-existing variable `{field}`"), &field.origin)
}
Self::ModifyUnsetFunction(field) | Self::PrintUnsetFunction(field) => {
(format!("non-existing function `{field}`"), &field.origin)
}
};
Annotation::new(AnnotationType::Error, message.into(), location)
}
fn additional_annotations<'a, T: Extend<Annotation<'a>>>(&'a self, results: &mut T) {
match self {
Self::AssignReadOnlyVariable(error) => error.additional_annotations(results),
Self::UndoReadOnlyVariable(error) => results.extend(std::iter::once(Annotation::new(
AnnotationType::Info,
"the variable was made read-only here".into(),
&error.read_only_location,
))),
Self::UndoReadOnlyFunction(error) => results.extend(std::iter::once(Annotation::new(
AnnotationType::Info,
"the function was made read-only here".into(),
&error.read_only_location,
))),
Self::ModifyUnsetFunction(_)
| Self::PrintUnsetVariable(_)
| Self::PrintUnsetFunction(_) => {}
}
}
}
impl std::fmt::Display for ExecuteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message_title().fmt(f)
}
}
pub async fn main(env: &mut Env, args: Vec<Field>) -> yash_env::builtin::Result {
match syntax::parse(syntax::ALL_OPTIONS, args) {
Ok((options, operands)) => match syntax::interpret(options, operands) {
Ok(command) => match command.execute(env, &PRINT_CONTEXT) {
Ok(result) => output(env, &result).await,
Err(errors) => report_failure(env, to_single_message(&errors).unwrap()).await,
},
Err(error) => report_error(env, &error).await,
},
Err(error) => report_error(env, &error).await,
}
}