use std::{collections::BTreeMap, error::Error, fmt};
use sim_kernel::{ShapeId, Symbol};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CallMode {
positional: bool,
named: bool,
}
impl CallMode {
pub const POSITIONAL: Self = Self::new(true, false);
pub const NAMED: Self = Self::new(false, true);
pub const POSITIONAL_OR_NAMED: Self = Self::new(true, true);
pub const fn new(positional: bool, named: bool) -> Self {
Self { positional, named }
}
pub const fn is_positional(self) -> bool {
self.positional
}
pub const fn is_named(self) -> bool {
self.named
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ParameterKind {
Required,
Optional,
Remainder,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ParameterDescriptor {
name: Symbol,
kind: ParameterKind,
call_mode: CallMode,
shape: Option<ShapeId>,
}
impl ParameterDescriptor {
pub fn new(
name: Symbol,
kind: ParameterKind,
call_mode: CallMode,
shape: Option<ShapeId>,
) -> Self {
Self {
name,
kind,
call_mode,
shape,
}
}
pub fn name(&self) -> &Symbol {
&self.name
}
pub const fn kind(&self) -> ParameterKind {
self.kind
}
pub const fn call_mode(&self) -> CallMode {
self.call_mode
}
pub const fn shape(&self) -> Option<ShapeId> {
self.shape
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CaptureDescriptor {
name: Symbol,
shape: Option<ShapeId>,
}
impl CaptureDescriptor {
pub fn new(name: Symbol, shape: Option<ShapeId>) -> Self {
Self { name, shape }
}
pub fn name(&self) -> &Symbol {
&self.name
}
pub const fn shape(&self) -> Option<ShapeId> {
self.shape
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BrowseProjection {
parameters: Vec<(Symbol, Option<ShapeId>)>,
result: Option<ShapeId>,
}
impl BrowseProjection {
pub fn parameters(&self) -> &[(Symbol, Option<ShapeId>)] {
&self.parameters
}
pub const fn result(&self) -> Option<ShapeId> {
self.result
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanError {
message: String,
}
impl PlanError {
fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for PlanError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl Error for PlanError {}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct FunctionPlan {
display_identity: Symbol,
parameters: Vec<ParameterDescriptor>,
captures: Vec<CaptureDescriptor>,
result_shape: Option<ShapeId>,
}
impl FunctionPlan {
pub fn new(
display_identity: Symbol,
parameters: Vec<ParameterDescriptor>,
captures: Vec<CaptureDescriptor>,
result_shape: Option<ShapeId>,
) -> Result<Self, PlanError> {
validate_parameters(¶meters)?;
validate_captures(&captures)?;
Ok(Self {
display_identity,
parameters,
captures,
result_shape,
})
}
pub fn display_identity(&self) -> &Symbol {
&self.display_identity
}
pub fn parameters(&self) -> &[ParameterDescriptor] {
&self.parameters
}
pub fn captures(&self) -> &[CaptureDescriptor] {
&self.captures
}
pub const fn result_shape(&self) -> Option<ShapeId> {
self.result_shape
}
pub fn browse(&self) -> BrowseProjection {
BrowseProjection {
parameters: self
.parameters
.iter()
.map(|p| (p.name.clone(), p.shape))
.collect(),
result: self.result_shape,
}
}
}
fn validate_parameters(parameters: &[ParameterDescriptor]) -> Result<(), PlanError> {
let mut names = BTreeMap::new();
let mut positional_remainder: Option<&Symbol> = None;
for parameter in parameters {
if let Some(first) = names.insert(parameter.name.clone(), parameter.name.clone()) {
return Err(PlanError::new(format!(
"duplicate parameter names {first} and {}",
parameter.name
)));
}
if !parameter.call_mode.positional && !parameter.call_mode.named {
return Err(PlanError::new(format!(
"parameter {} has contradictory call modes",
parameter.name
)));
}
if let Some(remainder) = positional_remainder
&& parameter.kind == ParameterKind::Required
&& parameter.call_mode.positional
{
return Err(PlanError::new(format!(
"positional remainder {remainder} cannot precede required parameter {}",
parameter.name
)));
}
if parameter.kind == ParameterKind::Remainder {
if parameter.call_mode == CallMode::POSITIONAL_OR_NAMED {
return Err(PlanError::new(format!(
"remainder parameter {} has contradictory call modes",
parameter.name
)));
}
if parameter.call_mode.positional {
if let Some(first) = positional_remainder {
return Err(PlanError::new(format!(
"positional remainders {first} and {} conflict",
parameter.name
)));
}
positional_remainder = Some(¶meter.name);
}
}
}
Ok(())
}
fn validate_captures(captures: &[CaptureDescriptor]) -> Result<(), PlanError> {
let mut names = BTreeMap::new();
for capture in captures {
if let Some(first) = names.insert(capture.name.clone(), capture.name.clone()) {
return Err(PlanError::new(format!(
"duplicate capture names {first} and {}",
capture.name
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn parameter(name: &str, kind: ParameterKind, mode: CallMode) -> ParameterDescriptor {
ParameterDescriptor::new(Symbol::new(name), kind, mode, None)
}
#[test]
fn remainder_before_required_names_both_parameters() {
let error = FunctionPlan::new(
Symbol::new("example"),
vec![
parameter("rest", ParameterKind::Remainder, CallMode::POSITIONAL),
parameter("needed", ParameterKind::Required, CallMode::POSITIONAL),
],
vec![],
None,
)
.unwrap_err();
assert!(error.to_string().contains("rest"));
assert!(error.to_string().contains("needed"));
}
#[test]
fn equal_declarations_have_equal_identity() {
let build = || {
FunctionPlan::new(
Symbol::qualified("guest", "work"),
vec![parameter(
"value",
ParameterKind::Required,
CallMode::POSITIONAL_OR_NAMED,
)],
vec![CaptureDescriptor::new(
Symbol::new("scope"),
Some(ShapeId(7)),
)],
Some(ShapeId(9)),
)
.unwrap()
};
assert_eq!(build(), build());
}
#[test]
fn construction_rejects_duplicates_and_contradictory_modes() {
let duplicate = FunctionPlan::new(
Symbol::new("duplicate"),
vec![
parameter("same", ParameterKind::Required, CallMode::NAMED),
parameter("same", ParameterKind::Optional, CallMode::NAMED),
],
vec![],
None,
)
.unwrap_err();
assert!(duplicate.to_string().contains("same"));
let contradictory = FunctionPlan::new(
Symbol::new("contradictory"),
vec![parameter(
"lost",
ParameterKind::Required,
CallMode::new(false, false),
)],
vec![],
None,
)
.unwrap_err();
assert!(contradictory.to_string().contains("lost"));
}
#[test]
fn browse_projection_preserves_shape_identifiers() {
let plan = FunctionPlan::new(
Symbol::new("browse"),
vec![ParameterDescriptor::new(
Symbol::new("input"),
ParameterKind::Required,
CallMode::POSITIONAL,
Some(ShapeId(3)),
)],
vec![],
Some(ShapeId(4)),
)
.unwrap();
assert_eq!(
plan.browse().parameters(),
&[(Symbol::new("input"), Some(ShapeId(3)))]
);
assert_eq!(plan.browse().result(), Some(ShapeId(4)));
}
}