1use std::{any::Any, collections::BTreeSet, sync::Arc};
9
10use crate::{
11 callable::Callable,
12 env::{Cx, Env},
13 error::{Diagnostic, Error, Result},
14 expr::Expr,
15 hint::{HintMetadata, diagnostic_hints_value},
16 id::{CORE_SHAPE_CLASS_ID, ShapeId, Symbol},
17 object::{Args, ClassRef, Object, RawArgs, ShapeRef},
18 value::Value,
19};
20
21pub trait Shape: Callable {
71 fn id(&self) -> Option<ShapeId> {
73 None
74 }
75
76 fn symbol(&self) -> Option<Symbol> {
78 None
79 }
80
81 fn parents(&self, _cx: &mut Cx) -> Result<Vec<ShapeRef>> {
83 Ok(Vec::new())
84 }
85
86 fn is_effectful(&self) -> bool {
88 false
89 }
90
91 fn is_total(&self) -> bool {
93 false
94 }
95
96 fn is_subshape_of(&self, _cx: &mut Cx, _parent: &dyn Shape) -> Result<Option<bool>> {
101 Ok(None)
102 }
103
104 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch>;
106 fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch>;
108 fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc>;
110}
111
112impl<T> Object for T
113where
114 T: Shape + Any,
115{
116 fn display(&self, cx: &mut Cx) -> Result<String> {
117 let doc = self.describe(cx)?;
118 match self.symbol() {
119 Some(symbol) => Ok(format!("#<shape {} {}>", symbol, doc.name)),
120 None => Ok(format!("#<shape {}>", doc.name)),
121 }
122 }
123
124 fn as_any(&self) -> &dyn Any {
125 self
126 }
127}
128
129impl<T> crate::ObjectCompat for T
130where
131 T: Shape + Any,
132{
133 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
134 let symbol = Symbol::qualified("core", "Shape");
135 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
136 return Ok(value.clone());
137 }
138 cx.factory().class_stub(CORE_SHAPE_CLASS_ID, symbol)
139 }
140 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
141 let doc = self.describe(cx)?;
142 let mut entries = vec![
143 (Symbol::new("name"), cx.factory().string(doc.name)?),
144 (
145 Symbol::new("effectful"),
146 cx.factory().bool(self.is_effectful())?,
147 ),
148 (Symbol::new("total"), cx.factory().bool(self.is_total())?),
149 ];
150 if let Some(symbol) = self.symbol() {
151 entries.push((
152 Symbol::new("symbol"),
153 cx.factory().string(symbol.to_string())?,
154 ));
155 }
156 for (index, detail) in doc.details.into_iter().enumerate() {
157 entries.push((
158 Symbol::qualified("detail", index.to_string()),
159 cx.factory().string(detail)?,
160 ));
161 }
162 cx.factory().table(entries)
163 }
164 fn as_shape(&self) -> Option<&dyn Shape> {
165 Some(self)
166 }
167}
168
169impl<T> Callable for T
170where
171 T: Shape,
172{
173 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
175 let [value] = args.values() else {
176 return Err(Error::Eval("shape call expects 1 argument".to_owned()));
177 };
178 call_shape(cx, self, ShapeCallTarget::Value(value.clone()))
179 }
180
181 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
183 let [expr] = args.exprs() else {
184 return Err(Error::Eval("shape call expects 1 expression".to_owned()));
185 };
186 call_shape(cx, self, ShapeCallTarget::Expr(expr.clone()))
187 }
188}
189
190#[derive(Clone, Debug, Default, PartialEq, Eq)]
192pub struct ShapeDoc {
193 pub name: String,
195 pub details: Vec<String>,
197}
198
199impl ShapeDoc {
200 pub fn new(name: impl Into<String>) -> Self {
202 Self {
203 name: name.into(),
204 details: Vec::new(),
205 }
206 }
207
208 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
210 self.details.push(detail.into());
211 self
212 }
213}
214
215#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
220pub struct MatchScore(i32);
221
222impl MatchScore {
223 pub fn exact(value: i32) -> Self {
225 Self(value)
226 }
227
228 pub fn reject() -> Self {
230 Self(i32::MIN / 2)
231 }
232
233 pub fn value(self) -> i32 {
235 self.0
236 }
237}
238
239impl core::ops::AddAssign for MatchScore {
240 fn add_assign(&mut self, rhs: Self) {
241 self.0 = self.0.saturating_add(rhs.0);
242 }
243}
244
245#[derive(Clone, Debug, Default)]
250pub struct ShapeBindings {
251 values: Vec<(Symbol, Value)>,
252 exprs: Vec<(Symbol, Expr)>,
253}
254
255impl ShapeBindings {
256 pub fn new() -> Self {
258 Self::default()
259 }
260
261 pub fn bind_value(&mut self, name: Symbol, value: Value) {
263 self.values.push((name, value));
264 }
265
266 pub fn bind_expr(&mut self, name: Symbol, expr: Expr) {
268 self.exprs.push((name, expr));
269 }
270
271 pub fn extend(&mut self, other: ShapeBindings) {
273 self.values.extend(other.values);
274 self.exprs.extend(other.exprs);
275 }
276
277 pub fn values(&self) -> &[(Symbol, Value)] {
279 &self.values
280 }
281
282 pub fn exprs(&self) -> &[(Symbol, Expr)] {
284 &self.exprs
285 }
286
287 pub fn into_env(self, cx: &mut Cx) -> Result<()> {
289 let env = self.into_child_env(cx)?;
290 *cx.env_mut() = env;
291 Ok(())
292 }
293
294 pub fn into_child_env(self, cx: &mut Cx) -> Result<Env> {
297 let mut env = Env::child(Arc::new(cx.env().clone()));
298 for (name, value) in self.values {
299 env.define(name, value);
300 }
301 for (name, expr) in self.exprs {
302 let value = cx.factory().expr(expr)?;
303 env.define(name, value);
304 }
305 Ok(env)
306 }
307}
308
309#[derive(Clone, Debug)]
328pub struct ShapeMatch {
329 pub accepted: bool,
331 pub captures: ShapeBindings,
333 pub score: MatchScore,
335 pub diagnostics: Vec<Diagnostic>,
337}
338
339impl ShapeMatch {
340 pub fn accept(score: MatchScore) -> Self {
342 Self {
343 accepted: true,
344 captures: ShapeBindings::new(),
345 score,
346 diagnostics: Vec::new(),
347 }
348 }
349
350 pub fn reject(message: impl Into<String>) -> Self {
352 Self {
353 accepted: false,
354 captures: ShapeBindings::new(),
355 score: MatchScore::reject(),
356 diagnostics: vec![Diagnostic::error(message)],
357 }
358 }
359
360 pub fn reject_with_diagnostic(diagnostic: Diagnostic) -> Self {
362 Self {
363 accepted: false,
364 captures: ShapeBindings::new(),
365 score: MatchScore::reject(),
366 diagnostics: vec![diagnostic],
367 }
368 }
369}
370
371#[derive(Clone, Debug)]
374pub struct ShapeMatchObject {
375 matched: ShapeMatch,
376}
377
378impl ShapeMatchObject {
379 pub fn new(matched: ShapeMatch) -> Self {
381 Self { matched }
382 }
383
384 pub fn matched(&self) -> &ShapeMatch {
386 &self.matched
387 }
388}
389
390impl Object for ShapeMatchObject {
391 fn display(&self, _cx: &mut Cx) -> Result<String> {
392 Ok(format!(
393 "#<shape-match {} score={}>",
394 if self.matched.accepted {
395 "accepted"
396 } else {
397 "rejected"
398 },
399 self.matched.score.value()
400 ))
401 }
402
403 fn as_any(&self) -> &dyn Any {
404 self
405 }
406}
407
408impl crate::ObjectCompat for ShapeMatchObject {
409 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
410 let symbol = Symbol::qualified("core", "ShapeMatch");
411 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
412 return Ok(value.clone());
413 }
414 cx.factory()
415 .class_stub(crate::id::CORE_SHAPE_MATCH_CLASS_ID, symbol)
416 }
417 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
418 Ok(self.matched.accepted)
419 }
420 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
421 shape_match_table(cx, &self.matched)
422 }
423 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
424 self.as_table(cx)?.object().as_expr(cx)
425 }
426}
427
428#[derive(Clone, Debug, PartialEq, Eq)]
434pub enum ExprKind {
435 Nil,
437 Bool,
439 Number,
441 Symbol,
443 String,
445 Bytes,
447 List,
449 Vector,
451 Map,
453 Set,
455 Call,
457 Infix,
459 Prefix,
461 Postfix,
463 Block,
465 Quote,
467 Annotated,
469 Extension,
471}
472
473impl ExprKind {
474 pub fn matches(&self, expr: &Expr) -> bool {
476 matches!(
477 (self, expr),
478 (Self::Nil, Expr::Nil)
479 | (Self::Bool, Expr::Bool(_))
480 | (Self::Number, Expr::Number(_))
481 | (Self::Symbol, Expr::Symbol(_))
482 | (Self::String, Expr::String(_))
483 | (Self::Bytes, Expr::Bytes(_))
484 | (Self::List, Expr::List(_))
485 | (Self::Vector, Expr::Vector(_))
486 | (Self::Map, Expr::Map(_))
487 | (Self::Set, Expr::Set(_))
488 | (Self::Call, Expr::Call { .. })
489 | (Self::Infix, Expr::Infix { .. })
490 | (Self::Prefix, Expr::Prefix { .. })
491 | (Self::Postfix, Expr::Postfix { .. })
492 | (Self::Block, Expr::Block(_))
493 | (Self::Quote, Expr::Quote { .. })
494 | (Self::Annotated, Expr::Annotated { .. })
495 | (Self::Extension, Expr::Extension { .. })
496 )
497 }
498
499 pub fn name(&self) -> &'static str {
501 match self {
502 Self::Nil => "nil",
503 Self::Bool => "bool",
504 Self::Number => "number",
505 Self::Symbol => "symbol",
506 Self::String => "string",
507 Self::Bytes => "bytes",
508 Self::List => "list",
509 Self::Vector => "vector",
510 Self::Map => "map",
511 Self::Set => "set",
512 Self::Call => "call",
513 Self::Infix => "infix",
514 Self::Prefix => "prefix",
515 Self::Postfix => "postfix",
516 Self::Block => "block",
517 Self::Quote => "quote",
518 Self::Annotated => "annotated",
519 Self::Extension => "extension",
520 }
521 }
522}
523
524#[derive(Clone, Debug)]
526pub enum ShapeCallTarget {
527 Value(Value),
529 Expr(Expr),
531}
532
533pub fn call_shape(cx: &mut Cx, shape: &dyn Shape, target: ShapeCallTarget) -> Result<Value> {
539 let matched = match target {
540 ShapeCallTarget::Value(value) => shape.check_value(cx, value)?,
541 ShapeCallTarget::Expr(expr) => shape.check_expr(cx, &expr)?,
542 };
543 shape_match_value(cx, matched)
544}
545
546pub fn shape_match_value(cx: &mut Cx, matched: ShapeMatch) -> Result<Value> {
548 cx.factory()
549 .opaque(Arc::new(ShapeMatchObject::new(matched)))
550}
551
552pub fn shape_is_subshape_of(cx: &mut Cx, child: &dyn Shape, parent: &dyn Shape) -> Result<bool> {
559 let mut seen = BTreeSet::new();
560 shape_is_subshape_of_inner(cx, child, parent, &mut seen)
561}
562
563fn shape_is_subshape_of_inner(
564 cx: &mut Cx,
565 child: &dyn Shape,
566 parent: &dyn Shape,
567 seen: &mut BTreeSet<ShapeIdentity>,
568) -> Result<bool> {
569 if let (Some(child_id), Some(parent_id)) = (child.id(), parent.id())
570 && child_id == parent_id
571 {
572 return Ok(true);
573 }
574 if let (Some(child_symbol), Some(parent_symbol)) = (child.symbol(), parent.symbol())
575 && child_symbol == parent_symbol
576 {
577 return Ok(true);
578 }
579 if let Some(answer) = child.is_subshape_of(cx, parent)? {
580 return Ok(answer);
581 }
582 if !seen.insert(shape_identity(child)) {
583 return Ok(false);
584 }
585 if matches!(
586 parent.symbol(),
587 Some(symbol)
588 if symbol == Symbol::qualified("core", "Any")
589 || symbol == Symbol::qualified("core", "AnyShape")
590 ) && !child.is_effectful()
591 {
592 return Ok(true);
593 }
594 for candidate in child.parents(cx)? {
595 let Some(candidate) = candidate.object().as_shape() else {
596 continue;
597 };
598 if shape_is_subshape_of_inner(cx, candidate, parent, seen)? {
599 return Ok(true);
600 }
601 }
602 Ok(false)
603}
604
605#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
606enum ShapeIdentity {
607 Id(ShapeId),
608 Symbol(Symbol),
609 Pointer(usize),
610}
611
612fn shape_identity(shape: &dyn Shape) -> ShapeIdentity {
613 if let Some(id) = shape.id() {
614 return ShapeIdentity::Id(id);
615 }
616 if let Some(symbol) = shape.symbol() {
617 return ShapeIdentity::Symbol(symbol);
618 }
619 ShapeIdentity::Pointer(shape as *const dyn Shape as *const () as usize)
620}
621
622fn shape_match_table(cx: &mut Cx, matched: &ShapeMatch) -> Result<Value> {
623 let value_captures = cx.factory().table(matched.captures.values().to_vec())?;
624 let expr_captures = cx.factory().table(
625 matched
626 .captures
627 .exprs()
628 .iter()
629 .map(|(symbol, expr)| Ok((symbol.clone(), cx.factory().expr(expr.clone())?)))
630 .collect::<Result<Vec<_>>>()?,
631 )?;
632 let diagnostics = matched
633 .diagnostics
634 .clone()
635 .into_iter()
636 .map(|diagnostic| diagnostic_value(cx, diagnostic))
637 .collect::<Result<Vec<_>>>()?;
638 let diagnostics = cx.factory().list(diagnostics)?;
639 cx.factory().table(vec![
640 (
641 Symbol::new("accepted"),
642 cx.factory().bool(matched.accepted)?,
643 ),
644 (
645 Symbol::new("score"),
646 cx.factory().number_literal(
647 Symbol::qualified("numbers", "f64"),
648 matched.score.value().to_string(),
649 )?,
650 ),
651 (Symbol::qualified("captures", "value"), value_captures),
652 (Symbol::qualified("captures", "expr"), expr_captures),
653 (Symbol::new("diagnostics"), diagnostics),
654 ])
655}
656
657fn diagnostic_value(cx: &mut Cx, diagnostic: Diagnostic) -> Result<Value> {
658 let hints = diagnostic_hints_value(cx, &diagnostic)?;
659 let severity = match diagnostic.severity {
660 crate::error::Severity::Error => "error",
661 crate::error::Severity::Warning => "warning",
662 crate::error::Severity::Info => "info",
663 crate::error::Severity::Note => "note",
664 };
665 let related = diagnostic
666 .related
667 .into_iter()
668 .filter(|related| !HintMetadata::is_hint_diagnostic(related))
669 .map(|related| diagnostic_value(cx, related))
670 .collect::<Result<Vec<_>>>()?;
671 let related = cx.factory().list(related)?;
672 let mut entries = vec![
673 (
674 Symbol::new("severity"),
675 cx.factory().symbol(Symbol::new(severity))?,
676 ),
677 (
678 Symbol::new("message"),
679 cx.factory().string(diagnostic.message)?,
680 ),
681 (Symbol::new("related"), related),
682 (Symbol::new("hints"), hints),
683 ];
684 if let Some(code) = diagnostic.code {
685 entries.push((Symbol::new("code"), cx.factory().symbol(code)?));
686 }
687 cx.factory().table(entries)
688}
689
690#[cfg(test)]
691#[path = "shape_tests.rs"]
692mod tests;