1use std::{collections::HashMap, rc::Rc};
7use thiserror::Error;
8
9use crate::ast::*;
10
11#[derive(Debug, thiserror::Error, miette::Diagnostic)]
12#[error("not in scope: {name}")]
13#[diagnostic(code(tx3::not_in_scope))]
14pub struct NotInScopeError {
15 pub name: String,
16
17 #[source_code]
18 src: Option<String>,
19
20 #[label]
21 span: Span,
22}
23
24#[derive(Debug, thiserror::Error, miette::Diagnostic)]
25#[error("invalid symbol, expected {expected}, got {got}")]
26#[diagnostic(code(tx3::invalid_symbol))]
27pub struct InvalidSymbolError {
28 pub expected: &'static str,
29 pub got: String,
30
31 #[source_code]
32 src: Option<String>,
33
34 #[label]
35 span: Span,
36}
37
38#[derive(Error, Debug, miette::Diagnostic)]
39pub enum Error {
40 #[error("duplicate definition: {0}")]
41 #[diagnostic(code(tx3::duplicate_definition))]
42 DuplicateDefinition(String),
43
44 #[error(transparent)]
45 #[diagnostic(transparent)]
46 NotInScope(#[from] NotInScopeError),
47
48 #[error("needs parent scope")]
49 #[diagnostic(code(tx3::needs_parent_scope))]
50 NeedsParentScope,
51
52 #[error(transparent)]
53 #[diagnostic(transparent)]
54 InvalidSymbol(#[from] InvalidSymbolError),
55}
56
57impl Error {
58 pub fn span(&self) -> &Span {
59 match self {
60 Self::NotInScope(x) => &x.span,
61 Self::InvalidSymbol(x) => &x.span,
62 _ => &Span::DUMMY,
63 }
64 }
65
66 pub fn src(&self) -> Option<&str> {
67 match self {
68 Self::NotInScope(x) => x.src.as_deref(),
69 _ => None,
70 }
71 }
72
73 pub fn not_in_scope(name: String, ast: &impl crate::parsing::AstNode) -> Self {
74 Self::NotInScope(NotInScopeError {
75 name,
76 src: None,
77 span: ast.span().clone(),
78 })
79 }
80
81 pub fn invalid_symbol(
82 expected: &'static str,
83 got: &Symbol,
84 ast: &impl crate::parsing::AstNode,
85 ) -> Self {
86 Self::InvalidSymbol(InvalidSymbolError {
87 expected,
88 got: format!("{:?}", got),
89 src: None,
90 span: ast.span().clone(),
91 })
92 }
93}
94
95#[derive(Debug, Default)]
96pub struct AnalyzeReport {
97 pub errors: Vec<Error>,
98}
99
100impl AnalyzeReport {
101 pub fn is_empty(&self) -> bool {
102 self.errors.is_empty()
103 }
104
105 pub fn ok(self) -> Result<(), Self> {
106 if self.is_empty() {
107 Ok(())
108 } else {
109 Err(self)
110 }
111 }
112}
113
114impl std::error::Error for AnalyzeReport {}
115
116impl std::fmt::Display for AnalyzeReport {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 write!(f, "AnalyzeReport {{ errors: {:?} }}", self.errors)
119 }
120}
121
122impl std::ops::Add for Error {
123 type Output = AnalyzeReport;
124
125 fn add(self, other: Self) -> Self::Output {
126 Self::Output {
127 errors: vec![self, other],
128 }
129 }
130}
131
132impl From<Error> for AnalyzeReport {
133 fn from(error: Error) -> Self {
134 Self {
135 errors: vec![error],
136 }
137 }
138}
139
140impl From<Vec<Error>> for AnalyzeReport {
141 fn from(errors: Vec<Error>) -> Self {
142 Self { errors }
143 }
144}
145
146impl std::ops::Add for AnalyzeReport {
147 type Output = AnalyzeReport;
148
149 fn add(self, other: Self) -> Self::Output {
150 [self, other].into_iter().collect()
151 }
152}
153
154impl FromIterator<Error> for AnalyzeReport {
155 fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
156 Self {
157 errors: iter.into_iter().collect(),
158 }
159 }
160}
161
162impl FromIterator<AnalyzeReport> for AnalyzeReport {
163 fn from_iter<T: IntoIterator<Item = AnalyzeReport>>(iter: T) -> Self {
164 Self {
165 errors: iter.into_iter().flat_map(|r| r.errors).collect(),
166 }
167 }
168}
169
170macro_rules! bail_report {
171 ($($args:expr),*) => {
172 { return AnalyzeReport::from(vec![$($args),*]); }
173 };
174}
175
176impl Scope {
177 pub fn new(parent: Option<Rc<Scope>>) -> Self {
178 Self {
179 symbols: HashMap::new(),
180 parent,
181 }
182 }
183
184 pub fn track_type_def(&mut self, type_: &TypeDef) {
185 self.symbols
186 .insert(type_.name.clone(), Symbol::TypeDef(Box::new(type_.clone())));
187 }
188
189 pub fn track_variant_case(&mut self, case: &VariantCase) {
190 self.symbols.insert(
191 case.name.clone(),
192 Symbol::VariantCase(Box::new(case.clone())),
193 );
194 }
195
196 pub fn track_record_field(&mut self, field: &RecordField) {
197 self.symbols.insert(
198 field.name.clone(),
199 Symbol::RecordField(Box::new(field.clone())),
200 );
201 }
202
203 pub fn track_party_def(&mut self, party: &PartyDef) {
204 self.symbols.insert(
205 party.name.clone(),
206 Symbol::PartyDef(Box::new(party.clone())),
207 );
208 }
209
210 pub fn track_policy_def(&mut self, policy: &PolicyDef) {
211 self.symbols.insert(
212 policy.name.clone(),
213 Symbol::PolicyDef(Box::new(policy.clone())),
214 );
215 }
216
217 pub fn track_asset_def(&mut self, asset: &AssetDef) {
218 self.symbols.insert(
219 asset.name.clone(),
220 Symbol::AssetDef(Box::new(asset.clone())),
221 );
222 }
223
224 pub fn track_param_var(&mut self, param: &str, r#type: Type) {
225 self.symbols.insert(
226 param.to_string(),
227 Symbol::ParamVar(param.to_string(), Box::new(r#type)),
228 );
229 }
230
231 pub fn track_input(&mut self, input: &InputBlock) {
232 self.symbols.insert(
233 input.name.clone(),
234 Symbol::Input(input.name.clone(), Box::new(input.datum_is())),
235 );
236 }
237
238 pub fn track_record_fields_for_type(&mut self, r#type: &Type) {
239 let schema = resolve_type_schema(r#type);
240
241 for (name, r#type) in schema {
242 self.track_record_field(&RecordField {
243 name,
244 r#type,
245 span: Span::DUMMY,
246 });
247 }
248 }
249
250 pub fn resolve(&self, name: &str) -> Option<Symbol> {
251 if let Some(symbol) = self.symbols.get(name) {
252 Some(symbol.clone())
253 } else if let Some(parent) = &self.parent {
254 parent.resolve(name)
255 } else {
256 None
257 }
258 }
259}
260
261fn resolve_type_schema(ty: &Type) -> Vec<(String, Type)> {
262 match ty {
263 Type::AnyAsset => {
264 vec![
265 ("amount".to_string(), Type::Int),
266 ("policy".to_string(), Type::Bytes),
267 ("asset_name".to_string(), Type::Bytes),
268 ]
269 }
270 Type::UtxoRef => {
271 vec![
272 ("tx_hash".to_string(), Type::Bytes),
273 ("output_index".to_string(), Type::Int),
274 ]
275 }
276 Type::Custom(identifier) => {
277 let def = identifier.symbol.as_ref().and_then(|s| s.as_type_def());
278
279 match def {
280 Some(ty) if ty.cases.len() == 1 => ty.cases[0]
281 .fields
282 .iter()
283 .map(|f| (f.name.clone(), f.r#type.clone()))
284 .collect(),
285 _ => vec![],
286 }
287 }
288 _ => vec![],
289 }
290}
291
292pub trait Analyzable {
297 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
305}
306
307impl<T: Analyzable> Analyzable for Option<T> {
308 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
309 if let Some(item) = self {
310 item.analyze(parent)
311 } else {
312 AnalyzeReport::default()
313 }
314 }
315}
316
317impl<T: Analyzable> Analyzable for Box<T> {
318 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
319 self.as_mut().analyze(parent)
320 }
321}
322
323impl<T: Analyzable> Analyzable for Vec<T> {
324 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
325 self.iter_mut()
326 .map(|item| item.analyze(parent.clone()))
327 .collect()
328 }
329}
330
331impl Analyzable for PolicyField {
332 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
333 match self {
334 PolicyField::Hash(x) => x.analyze(parent),
335 PolicyField::Script(x) => x.analyze(parent),
336 PolicyField::Ref(x) => x.analyze(parent),
337 }
338 }
339}
340impl Analyzable for PolicyConstructor {
341 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
342 self.fields.analyze(parent)
343 }
344}
345
346impl Analyzable for PolicyDef {
347 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
348 match &mut self.value {
349 PolicyValue::Constructor(x) => x.analyze(parent),
350 PolicyValue::Assign(_) => AnalyzeReport::default(),
351 }
352 }
353}
354
355impl Analyzable for DataBinaryOp {
356 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
357 let left = self.left.analyze(parent.clone());
358 let right = self.right.analyze(parent.clone());
359
360 left + right
361 }
362}
363
364impl Analyzable for RecordConstructorField {
365 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
366 let name = self.name.analyze(parent.clone());
367 let value = self.value.analyze(parent.clone());
368
369 name + value
370 }
371}
372
373impl Analyzable for VariantCaseConstructor {
374 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
375 let name = self.name.analyze(parent.clone());
376
377 let mut scope = Scope::new(parent);
378
379 let case = match &self.name.symbol {
380 Some(Symbol::VariantCase(x)) => x,
381 Some(x) => bail_report!(Error::invalid_symbol("VariantCase", x, &self.name)),
382 None => bail_report!(Error::not_in_scope(self.name.value.clone(), &self.name)),
383 };
384
385 for field in case.fields.iter() {
386 scope.track_record_field(field);
387 }
388
389 self.scope = Some(Rc::new(scope));
390
391 let fields = self.fields.analyze(self.scope.clone());
392
393 let spread = self.spread.analyze(self.scope.clone());
394
395 name + fields + spread
396 }
397}
398
399impl Analyzable for DatumConstructor {
400 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
401 let r#type = self.r#type.analyze(parent.clone());
402
403 let mut scope = Scope::new(parent);
404
405 let type_def = match &self.r#type.symbol {
406 Some(Symbol::TypeDef(x)) => x,
407 Some(x) => bail_report!(Error::invalid_symbol("TypeDef", x, &self.r#type)),
408 _ => unreachable!(),
409 };
410
411 for case in type_def.cases.iter() {
412 scope.track_variant_case(case);
413 }
414
415 self.scope = Some(Rc::new(scope));
416
417 let case = self.case.analyze(self.scope.clone());
418
419 r#type + case
420 }
421}
422
423impl Analyzable for DataExpr {
424 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
425 match self {
426 DataExpr::Constructor(x) => x.analyze(parent),
427 DataExpr::Identifier(x) => x.analyze(parent),
428 DataExpr::PropertyAccess(x) => x.analyze(parent),
429 DataExpr::BinaryOp(x) => x.analyze(parent),
430 _ => AnalyzeReport::default(),
431 }
432 }
433}
434
435impl Analyzable for AssetBinaryOp {
436 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
437 let left = self.left.analyze(parent.clone());
438 let right = self.right.analyze(parent.clone());
439
440 left + right
441 }
442}
443
444impl Analyzable for AssetConstructor {
445 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
446 let amount = self.amount.analyze(parent.clone());
447 let r#type = self.r#type.analyze(parent.clone());
448
449 amount + r#type
450 }
451}
452
453impl Analyzable for PropertyAccess {
454 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
455 let object = self.object.analyze(parent.clone());
456
457 let mut scope = Scope::new(parent);
458
459 if let Some(ty) = self.object.symbol.as_ref().and_then(|s| s.target_type()) {
460 scope.track_record_fields_for_type(&ty);
461 }
462
463 self.scope = Some(Rc::new(scope));
464
465 let path = self.path.analyze(self.scope.clone());
466
467 object + path
468 }
469}
470
471impl Analyzable for AssetExpr {
472 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
473 match self {
474 AssetExpr::Identifier(x) => x.analyze(parent),
475 AssetExpr::Constructor(x) => x.analyze(parent),
476 AssetExpr::BinaryOp(x) => x.analyze(parent),
477 AssetExpr::PropertyAccess(x) => x.analyze(parent),
478 }
479 }
480}
481
482impl Analyzable for AddressExpr {
483 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
484 match self {
485 AddressExpr::Identifier(x) => x.analyze(parent),
486 _ => AnalyzeReport::default(),
487 }
488 }
489}
490impl Analyzable for Identifier {
491 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
492 let symbol = parent.and_then(|p| p.resolve(&self.value));
493
494 if symbol.is_none() {
495 bail_report!(Error::not_in_scope(self.value.clone(), self));
496 }
497
498 self.symbol = symbol;
499
500 AnalyzeReport::default()
501 }
502}
503
504impl Analyzable for Type {
505 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
506 match self {
507 Type::Custom(x) => x.analyze(parent),
508 _ => AnalyzeReport::default(),
509 }
510 }
511}
512
513impl Analyzable for InputBlockField {
514 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
515 match self {
516 InputBlockField::From(x) => x.analyze(parent),
517 InputBlockField::DatumIs(x) => x.analyze(parent),
518 InputBlockField::MinAmount(x) => x.analyze(parent),
519 InputBlockField::Redeemer(x) => x.analyze(parent),
520 InputBlockField::Ref(x) => x.analyze(parent),
521 }
522 }
523}
524
525impl Analyzable for InputBlock {
526 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
527 self.fields.analyze(parent)
528 }
529}
530
531impl Analyzable for OutputBlockField {
532 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
533 match self {
534 OutputBlockField::To(x) => x.analyze(parent),
535 OutputBlockField::Amount(x) => x.analyze(parent),
536 OutputBlockField::Datum(x) => x.analyze(parent),
537 }
538 }
539}
540
541impl Analyzable for OutputBlock {
542 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
543 self.fields.analyze(parent)
544 }
545}
546
547impl Analyzable for RecordField {
548 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
549 self.r#type.analyze(parent)
550 }
551}
552
553impl Analyzable for VariantCase {
554 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
555 self.fields.analyze(parent)
556 }
557}
558
559impl Analyzable for TypeDef {
560 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
561 self.cases.analyze(parent)
562 }
563}
564
565impl Analyzable for MintBlockField {
566 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
567 match self {
568 MintBlockField::Amount(x) => x.analyze(parent),
569 MintBlockField::Redeemer(x) => x.analyze(parent),
570 }
571 }
572}
573
574impl Analyzable for MintBlock {
575 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
576 self.fields.analyze(parent)
577 }
578}
579
580impl Analyzable for ChainSpecificBlock {
581 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
582 match self {
583 ChainSpecificBlock::Cardano(x) => x.analyze(parent),
584 }
585 }
586}
587
588impl Analyzable for TxDef {
589 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
590 let mut scope = Scope::new(parent);
591
592 scope.symbols.insert("fees".to_string(), Symbol::Fees);
593
594 for param in self.parameters.parameters.iter() {
595 scope.track_param_var(¶m.name, param.r#type.clone());
596 }
597
598 for input in self.inputs.iter() {
599 scope.track_input(input);
600 }
601
602 self.scope = Some(Rc::new(scope));
603
604 let inputs = self.inputs.analyze(self.scope.clone());
605
606 let outputs = self.outputs.analyze(self.scope.clone());
607
608 let mint = self.mint.analyze(self.scope.clone());
609
610 let adhoc = self.adhoc.analyze(self.scope.clone());
611
612 inputs + outputs + mint + adhoc
613 }
614}
615
616static ADA: std::sync::LazyLock<AssetDef> = std::sync::LazyLock::new(|| AssetDef {
617 name: "Ada".to_string(),
618 policy: HexStringLiteral::new("".to_string()),
619 asset_name: "".to_string(),
620 span: Span::DUMMY,
621});
622
623impl Analyzable for Program {
624 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
625 let mut scope = Scope::new(parent);
626
627 for party in self.parties.iter() {
628 scope.track_party_def(party);
629 }
630
631 for policy in self.policies.iter() {
632 scope.track_policy_def(policy);
633 }
634
635 scope.track_asset_def(&ADA);
636
637 for asset in self.assets.iter() {
638 scope.track_asset_def(asset);
639 }
640
641 for type_def in self.types.iter() {
642 scope.track_type_def(type_def);
643 }
644
645 self.scope = Some(Rc::new(scope));
646
647 let policies = self.policies.analyze(self.scope.clone());
651
652 let types = self.types.analyze(self.scope.clone());
656
657 let txs = self.txs.analyze(self.scope.clone());
658
659 policies + types + txs
660 }
661}
662
663pub fn analyze(ast: &mut Program) -> AnalyzeReport {
677 ast.analyze(None)
678}