use self::syntax::OptionSpec;
use crate::common::output;
use crate::common::report::{merge_reports, report_error, report_failure};
use thiserror::Error;
use yash_env::Env;
use yash_env::function::Function;
use yash_env::option::State;
use yash_env::semantics::Field;
use yash_env::source::Location;
#[allow(deprecated)]
use yash_env::source::pretty::{Annotation, AnnotationType, MessageBase};
use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
use yash_env::variable::{Value, Variable};
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,
vacancy: None,
}
}
}
impl AssignReadOnlyError {
#[must_use]
pub fn to_report(&self) -> Report<'_> {
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = "error assigning to variable".into();
report.snippets =
Snippet::with_primary_span(&self.assigned_location, self.to_string().into());
add_span(
&self.read_only_location.code,
Span {
range: self.read_only_location.byte_range(),
role: SpanRole::Supplementary {
label: "the variable was made read-only here".into(),
},
},
&mut report.snippets,
);
report
}
}
impl<'a> From<&'a AssignReadOnlyError> for Report<'a> {
#[inline]
fn from(error: &'a AssignReadOnlyError) -> Self {
error.to_report()
}
}
#[allow(deprecated)]
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 std::fmt::Display for ExecuteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AssignReadOnlyVariable(e) => e.fmt(f),
Self::UndoReadOnlyVariable(e) => {
write!(f, "cannot cancel read-only-ness of variable `{}`", e.name)
}
Self::UndoReadOnlyFunction(e) => {
write!(f, "cannot cancel read-only-ness of function `{}`", e.name)
}
Self::ModifyUnsetFunction(field) => {
write!(f, "cannot modify non-existing function `{}`", field)
}
Self::PrintUnsetVariable(field) => {
write!(f, "cannot print non-existing variable `{}`", field)
}
Self::PrintUnsetFunction(field) => {
write!(f, "cannot print non-existing function `{}`", field)
}
}
}
}
impl ExecuteError {
#[must_use]
pub fn to_report(&self) -> Report<'_> {
let (title, location, label) = match self {
Self::AssignReadOnlyVariable(error) => return error.to_report(),
Self::UndoReadOnlyVariable(error) => (
"cannot cancel read-only-ness of variable",
&error.name.origin,
format!("read-only variable `{}`", error.name.value).into(),
),
Self::UndoReadOnlyFunction(error) => (
"cannot cancel read-only-ness of function",
&error.name.origin,
format!("read-only function `{}`", error.name.value).into(),
),
Self::ModifyUnsetFunction(field) => (
"cannot modify non-existing function",
&field.origin,
format!("non-existing function `{field}`").into(),
),
Self::PrintUnsetVariable(field) => (
"cannot print non-existing variable",
&field.origin,
format!("non-existing variable `{field}`").into(),
),
Self::PrintUnsetFunction(field) => (
"cannot print non-existing function",
&field.origin,
format!("non-existing function `{field}`").into(),
),
};
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = title.into();
report.snippets = Snippet::with_primary_span(location, label);
match self {
Self::UndoReadOnlyVariable(error) => add_span(
&error.read_only_location.code,
Span {
range: error.read_only_location.byte_range(),
role: SpanRole::Supplementary {
label: "the variable was made read-only here".into(),
},
},
&mut report.snippets,
),
Self::UndoReadOnlyFunction(error) => add_span(
&error.read_only_location.code,
Span {
range: error.read_only_location.byte_range(),
role: SpanRole::Supplementary {
label: "the function was made read-only here".into(),
},
},
&mut report.snippets,
),
_ => { }
}
report
}
}
impl<'a> From<&'a ExecuteError> for Report<'a> {
#[inline]
fn from(error: &'a ExecuteError) -> Self {
error.to_report()
}
}
#[allow(deprecated)]
impl MessageBase for ExecuteError {
fn message_title(&self) -> std::borrow::Cow<'_, str> {
self.to_string().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(_) => {}
}
}
}
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, merge_reports(&errors).unwrap()).await,
},
Err(error) => report_error(env, &error).await,
},
Err(error) => report_error(env, &error).await,
}
}