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