diff --git a/src/lib.rs b/src/lib.rs index 760857d..b5479a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ use tx3_lang::Protocol; mod cmds; mod server; +mod visitor; #[derive(Error, Debug)] pub enum Error { @@ -62,6 +63,22 @@ pub fn char_index_to_line_col(rope: &Rope, idx: usize) -> (usize, usize) { (line, col) } +pub fn position_to_offset(text: &str, position: Position) -> usize { + let mut offset = 0; + for (line_num, line) in text.lines().enumerate() { + if line_num == position.line as usize { + offset += position.character.min(line.len() as u32) as usize; + break; + } + offset += line.len() + 1; + } + offset +} + +pub fn span_contains(span: &tx3_lang::ast::Span, offset: usize) -> bool { + offset >= span.start && offset < span.end +} + pub fn span_to_lsp_range(rope: &Rope, loc: &tx3_lang::ast::Span) -> Range { let (start_line, start_col) = char_index_to_line_col(rope, loc.start); let (end_line, end_col) = char_index_to_line_col(rope, loc.end); @@ -117,6 +134,166 @@ pub struct Context { } impl Context { + fn is_type_field_reference( + ast: &tx3_lang::ast::Program, + identifier: &str, + offset: usize, + ) -> bool { + for type_def in &ast.types { + if crate::span_contains(&type_def.span, offset) { + for case in &type_def.cases { + for field in &case.fields { + if identifier == field.r#type.clone().to_str() { + return true; + } + } + } + } + } + false + } + fn collect_semantic_tokens( + &self, + ast: &tx3_lang::ast::Program, + rope: &Rope, + ) -> Vec { + const TOKEN_TYPE: u32 = 0; + const TOKEN_PARAMETER: u32 = 1; + const TOKEN_VARIABLE: u32 = 2; + const TOKEN_CLASS: u32 = 3; + const TOKEN_PARTY: u32 = 4; + const TOKEN_POLICY: u32 = 5; + const TOKEN_FUNCTION: u32 = 6; + // const TOKEN_KEYWORD: u32 = 7; + // const TOKEN_PROPERTY: u32 = 8; + + const MOD_DECLARATION: u32 = 1 << 0; + const MOD_DEFINITION: u32 = 1 << 1; + + #[derive(Debug, Clone)] + struct TokenInfo { + range: Range, + token_type: u32, + token_modifiers: u32, + } + + let mut token_infos: Vec = Vec::new(); + let text = rope.to_string(); + + let mut processed_spans = std::collections::HashSet::new(); + + for offset in 0..text.len() { + if let Some(symbol) = crate::visitor::find_symbol_in_program(ast, offset) { + match symbol { + crate::visitor::SymbolAtOffset::Identifier(identifier) => { + // Skip if we've already processed this exact span + let span_key = (identifier.span.start, identifier.span.end); + if processed_spans.contains(&span_key) { + continue; + } + processed_spans.insert(span_key); + + let token_type = if ast + .parties + .iter() + .any(|p| p.name.value == identifier.value) + { + TOKEN_PARTY + } else if ast + .policies + .iter() + .any(|p| p.name.value == identifier.value) + { + TOKEN_POLICY + } else if ast.types.iter().any(|t| t.name.value == identifier.value) { + TOKEN_TYPE + } else if Context::is_type_field_reference(ast, &identifier.value, offset) { + TOKEN_TYPE + } else if ast.assets.iter().any(|a| a.name.value == identifier.value) { + TOKEN_CLASS + } else { + let mut found_type = None; + + for tx in &ast.txs { + if tx.name.value == identifier.value { + found_type = Some(TOKEN_FUNCTION); + break; + } + + if crate::span_contains(&tx.span, offset) { + for param in &tx.parameters.parameters { + if param.name.value == identifier.value { + found_type = Some(TOKEN_PARAMETER); + break; + } + } + } + + if found_type.is_some() { + break; + } + } + found_type.unwrap_or(TOKEN_VARIABLE) + }; + + token_infos.push(TokenInfo { + range: crate::span_to_lsp_range(rope, &identifier.span), + token_type, + token_modifiers: MOD_DECLARATION | MOD_DEFINITION, + }); + } + visitor::SymbolAtOffset::TypeIdentifier(x) => { + token_infos.push(TokenInfo { + range: crate::span_to_lsp_range(rope, &x.span), + token_type: TOKEN_TYPE, + token_modifiers: MOD_DECLARATION | MOD_DEFINITION, + }); + } + } + } + } + token_infos.sort_by(|a, b| match a.range.start.line.cmp(&b.range.start.line) { + std::cmp::Ordering::Equal => a.range.start.character.cmp(&b.range.start.character), + other => other, + }); + + token_infos.dedup_by(|a, b| a.range.start == b.range.start && a.range.end == b.range.end); + + let mut semantic_tokens = Vec::new(); + let mut prev_line = 0; + let mut prev_start = 0; + + for token_info in token_infos { + let line = token_info.range.start.line; + let start = token_info.range.start.character; + let length = token_info.range.end.character.saturating_sub(start); + + if length == 0 { + continue; + } + + let delta_line = line.saturating_sub(prev_line); + let delta_start = if delta_line == 0 { + start.saturating_sub(prev_start) + } else { + start + }; + + semantic_tokens.push(SemanticToken { + delta_line, + delta_start, + length, + token_type: token_info.token_type, + token_modifiers_bitset: token_info.token_modifiers, + }); + + prev_line = line; + prev_start = start; + } + + semantic_tokens + } + pub fn new_for_client(client: Client) -> Self { Self { client, diff --git a/src/server.rs b/src/server.rs index 44feac2..2464962 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,11 @@ use serde_json::Value; use tower_lsp::{jsonrpc::Result, lsp_types::*, LanguageServer}; -use crate::{cmds, span_to_lsp_range, Context}; +use crate::{ + cmds, position_to_offset, span_contains, span_to_lsp_range, + visitor::{find_symbol_in_program, SymbolAtOffset}, + Context, +}; #[tower_lsp::async_trait] impl LanguageServer for Context { @@ -18,6 +22,34 @@ impl LanguageServer for Context { text_document_sync: Some(TextDocumentSyncCapability::Kind( TextDocumentSyncKind::FULL, )), + semantic_tokens_provider: Some( + SemanticTokensServerCapabilities::SemanticTokensOptions( + SemanticTokensOptions { + work_done_progress_options: WorkDoneProgressOptions::default(), + legend: SemanticTokensLegend { + token_types: vec![ + SemanticTokenType::TYPE, + SemanticTokenType::PARAMETER, + SemanticTokenType::VARIABLE, + SemanticTokenType::CLASS, + SemanticTokenType::new("party"), + SemanticTokenType::new("policy"), + SemanticTokenType::FUNCTION, + // SemanticTokenType::KEYWORD, + // SemanticTokenType::PROPERTY, + ], + token_modifiers: vec![ + SemanticTokenModifier::DECLARATION, + SemanticTokenModifier::DEFINITION, + SemanticTokenModifier::READONLY, + SemanticTokenModifier::STATIC, + ], + }, + range: Some(true), + full: Some(SemanticTokensFullOptions::Bool(true)), + }, + ), + ), execute_command_provider: Some(ExecuteCommandOptions { commands: vec!["generate-tir".to_string(), "generate-ast".to_string()], work_done_progress_options: WorkDoneProgressOptions { @@ -44,11 +76,144 @@ impl LanguageServer for Context { Ok(Some(CompletionResponse::Array(vec![]))) } + async fn semantic_tokens_full( + &self, + params: SemanticTokensParams, + ) -> Result> { + let uri = ¶ms.text_document.uri; + let document = self.documents.get(uri); + + if let Some(document) = document { + let text = document.value().to_string(); + let rope = document.value(); + + let ast = match tx3_lang::parsing::parse_string(text.as_str()) { + Ok(ast) => ast, + Err(_) => return Ok(None), + }; + + let tokens = self.collect_semantic_tokens(&ast, rope); + + Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { + result_id: None, + data: tokens, + }))) + } else { + Ok(None) + } + } + + async fn semantic_tokens_range( + &self, + params: SemanticTokensRangeParams, + ) -> Result> { + // TODO: optimize this for the specific range + let full_params = SemanticTokensParams { + text_document: params.text_document, + work_done_progress_params: params.work_done_progress_params, + partial_result_params: params.partial_result_params, + }; + + self.semantic_tokens_full(full_params).await.map(|result| { + result.map(|tokens| match tokens { + SemanticTokensResult::Tokens(t) => SemanticTokensRangeResult::Tokens(t), + SemanticTokensResult::Partial(p) => SemanticTokensRangeResult::Partial(p), + }) + }) + } + async fn goto_definition( &self, - _: GotoDefinitionParams, + params: GotoDefinitionParams, ) -> Result> { - // Return None for now, indicating no definition found + let uri = ¶ms.text_document_position_params.text_document.uri; + let position = params.text_document_position_params.position; + + let document = self.documents.get(uri); + if let Some(document) = document { + let text = document.value().to_string(); + + let ast = match tx3_lang::parsing::parse_string(text.as_str()) { + Ok(ast) => ast, + Err(_) => return Ok(None), + }; + + let offset = position_to_offset(&text, position); + + if let Some(symbol) = find_symbol_in_program(&ast, offset) { + let identifier = match symbol { + SymbolAtOffset::Identifier(x) => x, + SymbolAtOffset::TypeIdentifier(type_record) => { + // TODO - look only in type definitions + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &type_record.span), + }))); + } + }; + + for party in &ast.parties { + if party.name.value == identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &party.span), + }))); + } + } + + for policy in &ast.policies { + if policy.name.value == identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &policy.span), + }))); + } + } + + for tx in &ast.txs { + if span_contains(&tx.span, offset) { + for param in &tx.parameters.parameters { + if param.name.value == identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &tx.parameters.span), + }))); + } + } + + for input in &tx.inputs { + if input.name == identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &input.span), + }))); + } + } + + for output in &tx.outputs { + if let Some(output_name) = &output.name { + if output_name == &identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &output.span), + }))); + } + } + } + + for reference in &tx.references { + if reference.name == identifier.value { + return Ok(Some(GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: span_to_lsp_range(document.value(), &reference.span), + }))); + } + } + } + } + } + } + Ok(None) } @@ -58,31 +223,165 @@ impl LanguageServer for Context { } async fn hover(&self, params: HoverParams) -> Result> { - // Get the position where the user is hovering + let uri = ¶ms.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; - // Here you would typically: - // 1. Parse the document to identify the symbol at the hover position - // 2. Look up information about that symbol - // 3. Return a Hover object with the information + let document = self.documents.get(uri); + if let Some(document) = document { + let text = document.value().to_string(); - // For now, let's return a simple example hover - Ok(Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: "This is a symbol hover example".to_string(), - }), - range: Some(Range { - start: Position { - line: position.line, - character: position.character, - }, - end: Position { - line: position.line, - character: position.character + 1, - }, - }), - })) + let ast = match tx3_lang::parsing::parse_string(text.as_str()) { + Ok(ast) => ast, + Err(_) => return Ok(None), + }; + + let offset = position_to_offset(&text, position); + + for party in &ast.parties { + if span_contains(&party.span, offset) { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "**Party**: `{}`\n\nA party in the transaction. It can be an address for a script or a wallet.", + party.name.value + ), + }), + range: Some(span_to_lsp_range(document.value(), &party.span)), + })); + } + } + + for policy in &ast.policies { + if span_contains(&policy.span, offset) { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "**Policy**: `{}`\n\nA policy definition.", + policy.name.value + ), + }), + range: Some(span_to_lsp_range(document.value(), &policy.span)), + })); + } + } + + for type_def in &ast.types { + if span_contains(&type_def.span, offset) { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "**Type**: `{}`\n\nA type definition.", + type_def.name.value + ), + }), + range: Some(span_to_lsp_range(document.value(), &type_def.span)), + })); + } + } + + for asset in &ast.assets { + if span_contains(&asset.span, offset) { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "**Asset**: `{}`\n\nAn asset definition.", + asset.name.value + ), + }), + range: Some(span_to_lsp_range(document.value(), &asset.span)), + })); + } + } + + for tx in &ast.txs { + for input in &tx.inputs { + if span_contains(&input.span, offset) { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("**Input**: `{}`\n\nTransaction input.", input.name), + }), + range: Some(span_to_lsp_range(document.value(), &input.span)), + })); + } + } + + for output in &tx.outputs { + if span_contains(&output.span, offset) { + let default_output = "output".to_string(); + let name = output.name.as_ref().unwrap_or(&default_output); + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("**Output**: `{}`\n\nTransaction output.", name), + }), + range: Some(span_to_lsp_range(document.value(), &output.span)), + })); + } + } + + if span_contains(&tx.parameters.span, offset) { + for param in &tx.parameters.parameters { + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "**Parameter**: `{}`\n\n**Type**: `{:?}`", + param.name.value, param.r#type + ), + }), + range: Some(span_to_lsp_range(document.value(), &tx.parameters.span)), + })); + } + } + + if span_contains(&tx.span, offset) { + let mut hover_text = format!("**Transaction**: `{}`\n\n", tx.name.value); + + if !tx.parameters.parameters.is_empty() { + hover_text.push_str("**Parameters**:\n"); + for param in &tx.parameters.parameters { + hover_text.push_str(&format!( + "- `{}`: `{:?}`\n", + param.name.value, param.r#type + )); + } + hover_text.push_str("\n"); + } + + if !tx.inputs.is_empty() { + hover_text.push_str("**Inputs**:\n"); + for input in &tx.inputs { + hover_text.push_str(&format!("- `{}`\n", input.name)); + } + hover_text.push_str("\n"); + } + + if !tx.outputs.is_empty() { + hover_text.push_str("**Outputs**:\n"); + for output in &tx.outputs { + let default_output = "output".to_string(); + let name = output.name.as_ref().unwrap_or(&default_output); + hover_text.push_str(&format!("- `{}`\n", name)); + } + } + + return Ok(Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: hover_text, + }), + range: Some(span_to_lsp_range(document.value(), &tx.span)), + })); + } + } + } + + Ok(None) } // TODO: Add error handling and improve @@ -120,7 +419,7 @@ impl LanguageServer for Context { let ast = ast.unwrap(); for party in ast.parties { symbols.push(make_symbol( - party.name.clone(), + party.name.value.clone(), "Party".to_string(), SymbolKind::OBJECT, span_to_lsp_range(document.value(), &party.span), @@ -130,7 +429,7 @@ impl LanguageServer for Context { for policy in ast.policies { symbols.push(make_symbol( - policy.name.clone(), + policy.name.value.clone(), "Policy".to_string(), SymbolKind::KEY, span_to_lsp_range(document.value(), &policy.span), @@ -142,7 +441,7 @@ impl LanguageServer for Context { let mut children: Vec = Vec::new(); for parameter in tx.parameters.parameters { children.push(make_symbol( - parameter.name.clone(), + parameter.name.value.clone(), format!("Parameter<{:?}>", parameter.r#type), SymbolKind::FIELD, span_to_lsp_range(document.value(), &tx.parameters.span), @@ -171,7 +470,7 @@ impl LanguageServer for Context { } symbols.push(make_symbol( - tx.name.clone(), + tx.name.value.clone(), "Tx".to_string(), SymbolKind::METHOD, span_to_lsp_range(document.value(), &tx.span), diff --git a/src/visitor.rs b/src/visitor.rs new file mode 100644 index 0000000..e9d2325 --- /dev/null +++ b/src/visitor.rs @@ -0,0 +1,463 @@ +#[derive(Debug)] +pub enum SymbolAtOffset<'a> { + Identifier(&'a tx3_lang::ast::Identifier), + TypeIdentifier(&'a tx3_lang::ast::TypeRecord), +} + +pub fn find_symbol_in_program<'a>( + program: &'a tx3_lang::ast::Program, + offset: usize, +) -> Option> { + for tx in &program.txs { + if let Some(sym) = visit_tx_def(tx, offset) { + return Some(sym); + } + } + for asset in &program.assets { + if let Some(sym) = visit_asset_def(asset, offset) { + return Some(sym); + } + } + for ty in &program.types { + if let Some(sym) = visit_type_def(ty, offset) { + return Some(sym); + } + } + for party in &program.parties { + if let Some(sym) = visit_party_def(party, offset) { + return Some(sym); + } + } + for policy in &program.policies { + if let Some(sym) = visit_policy_def(policy, offset) { + return Some(sym); + } + } + None +} + +fn visit_tx_def<'a>(tx: &'a tx3_lang::ast::TxDef, offset: usize) -> Option> { + if in_span(&tx.name.span, offset) { + return Some(SymbolAtOffset::Identifier(&tx.name)); + } + if let Some(sym) = visit_parameter_list(&tx.parameters, offset) { + return Some(sym); + } + for input in &tx.inputs { + if let Some(sym) = visit_input_block(input, offset) { + return Some(sym); + } + } + for output in &tx.outputs { + if let Some(sym) = visit_output_block(output, offset) { + return Some(sym); + } + } + for mint in &tx.mints { + if let Some(sym) = visit_mint_block(mint, offset) { + return Some(sym); + } + } + for ref_block in &tx.references { + if let Some(sym) = visit_reference_block(ref_block, offset) { + return Some(sym); + } + } + for adhoc in &tx.adhoc { + if let Some(sym) = visit_chain_specific_block(adhoc, offset) { + return Some(sym); + } + } + for col in &tx.collateral { + if let Some(sym) = visit_collateral_block(col, offset) { + return Some(sym); + } + } + if let Some(signers) = &tx.signers { + if let Some(sym) = visit_signers_block(signers, offset) { + return Some(sym); + } + } + if let Some(validity) = &tx.validity { + if let Some(sym) = visit_validity_block(validity, offset) { + return Some(sym); + } + } + if let Some(burn) = &tx.burn { + if let Some(sym) = visit_burn_block(burn, offset) { + return Some(sym); + } + } + if let Some(metadata) = &tx.metadata { + if let Some(sym) = visit_metadata_block(metadata, offset) { + return Some(sym); + } + } + None +} + +fn visit_parameter_list<'a>( + params: &'a tx3_lang::ast::ParameterList, + offset: usize, +) -> Option> { + for param in ¶ms.parameters { + if in_span(¶m.name.span, offset) { + return Some(SymbolAtOffset::Identifier(¶m.name)); + } + if let Some(sym) = visit_type(¶m.r#type, offset) { + return Some(sym); + } + } + None +} + +fn visit_type<'a>(ty: &'a tx3_lang::ast::TypeRecord, offset: usize) -> Option> { + // TODO - complete for all types + match &ty.r#type { + tx3_lang::ast::Type::Custom(id) => visit_identifier(id, offset), + tx3_lang::ast::Type::List(inner) => visit_type(inner, offset), + _ => None, + } +} + +fn visit_identifier<'a>( + id: &'a tx3_lang::ast::Identifier, + offset: usize, +) -> Option> { + if in_span(&id.span, offset) { + Some(SymbolAtOffset::Identifier(id)) + } else { + None + } +} + +fn visit_input_block<'a>( + input: &'a tx3_lang::ast::InputBlock, + offset: usize, +) -> Option> { + for field in &input.fields { + if let Some(sym) = visit_input_block_field(field, offset) { + return Some(sym); + } + } + None +} + +fn visit_input_block_field<'a>( + field: &'a tx3_lang::ast::InputBlockField, + offset: usize, +) -> Option> { + match field { + tx3_lang::ast::InputBlockField::From(addr) => visit_address_expr(addr, offset), + tx3_lang::ast::InputBlockField::DatumIs(ty) => visit_type(ty, offset), + tx3_lang::ast::InputBlockField::MinAmount(expr) => visit_data_expr(expr, offset), + tx3_lang::ast::InputBlockField::Redeemer(expr) => visit_data_expr(expr, offset), + tx3_lang::ast::InputBlockField::Ref(expr) => visit_data_expr(expr, offset), + } +} + +fn visit_output_block<'a>( + output: &'a tx3_lang::ast::OutputBlock, + offset: usize, +) -> Option> { + for field in &output.fields { + if let Some(sym) = visit_output_block_field(field, offset) { + return Some(sym); + } + } + None +} + +fn visit_output_block_field<'a>( + field: &'a tx3_lang::ast::OutputBlockField, + offset: usize, +) -> Option> { + match field { + tx3_lang::ast::OutputBlockField::To(addr) => visit_address_expr(addr, offset), + tx3_lang::ast::OutputBlockField::Amount(expr) => visit_data_expr(expr, offset), + tx3_lang::ast::OutputBlockField::Datum(expr) => visit_data_expr(expr, offset), + } +} + +fn visit_data_expr<'a>( + expr: &'a tx3_lang::ast::DataExpr, + offset: usize, +) -> Option> { + match expr { + tx3_lang::ast::DataExpr::Identifier(id) => visit_identifier(id, offset), + tx3_lang::ast::DataExpr::StructConstructor(sc) => visit_struct_constructor(sc, offset), + tx3_lang::ast::DataExpr::ListConstructor(lc) => { + for el in &lc.elements { + if let Some(sym) = visit_data_expr(el, offset) { + return Some(sym); + } + } + None + } + _ => None, + } +} + +fn visit_struct_constructor<'a>( + sc: &'a tx3_lang::ast::StructConstructor, + offset: usize, +) -> Option> { + if let Some(sym) = visit_identifier(&sc.r#type, offset) { + return Some(sym); + } + visit_variant_case_constructor(&sc.case, offset) +} + +fn visit_variant_case_constructor<'a>( + vc: &'a tx3_lang::ast::VariantCaseConstructor, + offset: usize, +) -> Option> { + if let Some(sym) = visit_identifier(&vc.name, offset) { + return Some(sym); + } + for field in &vc.fields { + if let Some(sym) = visit_record_constructor_field(field, offset) { + return Some(sym); + } + } + if let Some(spread) = &vc.spread { + return visit_data_expr(spread, offset); + } + None +} + +fn visit_record_constructor_field<'a>( + field: &'a tx3_lang::ast::RecordConstructorField, + offset: usize, +) -> Option> { + if let Some(sym) = visit_identifier(&field.name, offset) { + return Some(sym); + } + visit_data_expr(&field.value, offset) +} + +fn visit_reference_block<'a>( + rb: &'a tx3_lang::ast::ReferenceBlock, + offset: usize, +) -> Option> { + visit_data_expr(&rb.r#ref, offset) +} + +fn visit_chain_specific_block<'a>( + _cb: &'a tx3_lang::ast::ChainSpecificBlock, + _offset: usize, +) -> Option> { + None +} + +fn visit_collateral_block<'a>( + cb: &'a tx3_lang::ast::CollateralBlock, + offset: usize, +) -> Option> { + for field in &cb.fields { + match field { + tx3_lang::ast::CollateralBlockField::From(addr) => { + if let Some(sym) = visit_address_expr(addr, offset) { + return Some(sym); + } + } + tx3_lang::ast::CollateralBlockField::MinAmount(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + tx3_lang::ast::CollateralBlockField::Ref(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + } + } + None +} + +fn visit_signers_block<'a>( + sb: &'a tx3_lang::ast::SignersBlock, + offset: usize, +) -> Option> { + for signer in &sb.signers { + if let Some(sym) = visit_data_expr(signer, offset) { + return Some(sym); + } + } + None +} + +fn visit_validity_block<'a>( + vb: &'a tx3_lang::ast::ValidityBlock, + offset: usize, +) -> Option> { + for field in &vb.fields { + match field { + tx3_lang::ast::ValidityBlockField::SinceSlot(expr) + | tx3_lang::ast::ValidityBlockField::UntilSlot(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + } + } + None +} + +fn visit_burn_block<'a>( + bb: &'a tx3_lang::ast::BurnBlock, + offset: usize, +) -> Option> { + for field in &bb.fields { + match field { + tx3_lang::ast::MintBlockField::Amount(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + tx3_lang::ast::MintBlockField::Redeemer(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + } + } + None +} + +fn visit_metadata_block<'a>( + _mb: &'a tx3_lang::ast::MetadataBlock, + _offset: usize, +) -> Option> { + None +} + +fn visit_mint_block<'a>( + mb: &'a tx3_lang::ast::MintBlock, + offset: usize, +) -> Option> { + for field in &mb.fields { + match field { + tx3_lang::ast::MintBlockField::Amount(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + tx3_lang::ast::MintBlockField::Redeemer(expr) => { + if let Some(sym) = visit_data_expr(expr, offset) { + return Some(sym); + } + } + } + } + None +} + +fn visit_asset_def<'a>( + asset: &'a tx3_lang::ast::AssetDef, + offset: usize, +) -> Option> { + if let Some(sym) = visit_data_expr(&asset.policy, offset) { + return Some(sym); + } + if let Some(sym) = visit_data_expr(&asset.asset_name, offset) { + return Some(sym); + } + None +} + +fn visit_type_def<'a>(ty: &'a tx3_lang::ast::TypeDef, offset: usize) -> Option> { + if in_span(&ty.name.span, offset) { + return Some(SymbolAtOffset::Identifier(&ty.name)); + } + for case in &ty.cases { + for field in &case.fields { + if in_span(&field.r#type.span, offset) { + return Some(SymbolAtOffset::TypeIdentifier(&field.r#type)); + } + } + if let Some(sym) = visit_variant_case(case, offset) { + return Some(sym); + } + } + None +} + +fn visit_variant_case<'a>( + case: &'a tx3_lang::ast::VariantCase, + offset: usize, +) -> Option> { + for field in &case.fields { + if let Some(sym) = visit_record_field(field, offset) { + return Some(sym); + } + } + None +} + +fn visit_record_field<'a>( + field: &'a tx3_lang::ast::RecordField, + offset: usize, +) -> Option> { + if in_span(&field.name.span, offset) { + return Some(SymbolAtOffset::Identifier(&field.name)); + } + visit_type(&field.r#type, offset) +} + +fn visit_party_def<'a>( + party: &'a tx3_lang::ast::PartyDef, + offset: usize, +) -> Option> { + if in_span(&party.span, offset) { + return Some(SymbolAtOffset::Identifier(&party.name)); + } + None +} + +fn visit_policy_def<'a>( + policy: &'a tx3_lang::ast::PolicyDef, + offset: usize, +) -> Option> { + match &policy.value { + tx3_lang::ast::PolicyValue::Constructor(constr) => { + for field in &constr.fields { + if let Some(sym) = visit_policy_field(field, offset) { + return Some(sym); + } + } + } + tx3_lang::ast::PolicyValue::Assign(_) => { + if in_span(&policy.span, offset) { + return Some(SymbolAtOffset::Identifier(&policy.name)); + } + } + } + None +} + +fn visit_policy_field<'a>( + field: &'a tx3_lang::ast::PolicyField, + offset: usize, +) -> Option> { + match field { + tx3_lang::ast::PolicyField::Hash(expr) => visit_data_expr(expr, offset), + tx3_lang::ast::PolicyField::Script(expr) => visit_data_expr(expr, offset), + tx3_lang::ast::PolicyField::Ref(expr) => visit_data_expr(expr, offset), + } +} + +fn visit_address_expr<'a>( + expr: &'a tx3_lang::ast::AddressExpr, + offset: usize, +) -> Option> { + match expr { + tx3_lang::ast::AddressExpr::Identifier(id) => visit_identifier(id, offset), + _ => None, + } +} + +fn in_span(span: &tx3_lang::ast::Span, offset: usize) -> bool { + span.start <= offset && offset < span.end +}