use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation {
pub file: String,
pub line: usize,
pub column: usize,
}
impl SourceLocation {
pub fn new(file: String, line: usize, column: usize) -> Self {
Self { file, line, column }
}
pub fn at_start(file: String) -> Self {
Self { file, line: 1, column: 1 }
}
pub fn without_file(line: usize, column: usize) -> Self {
Self {
file: String::new(),
line,
column,
}
}
}
pub struct SourceMapper<'a> {
source: &'a str,
}
impl<'a> SourceMapper<'a> {
pub fn new(source: &'a str) -> Self {
Self { source }
}
pub fn span_to_location(&self, span: &Range<usize>, file: String) -> SourceLocation {
let (line, column) = self.span_to_position(span);
SourceLocation::new(file, line, column)
}
pub fn span_to_position(&self, span: &Range<usize>) -> (usize, usize) {
let start = span.start;
let mut line = 1;
let mut col = 1;
for (i, ch) in self.source.char_indices() {
if i >= start {
break;
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}
pub fn optional_span_to_location(
&self,
span: Option<&Range<usize>>,
file: String,
) -> SourceLocation {
match span {
Some(s) => self.span_to_location(s, file),
None => SourceLocation::at_start(file),
}
}
pub fn optional_span_to_position(&self, span: Option<&Range<usize>>) -> (usize, usize) {
span.map(|s| self.span_to_position(s)).unwrap_or((1, 1))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockContext {
Action(String),
Variable(String),
Signer(String),
Output(String),
Flow(String),
Addon(String),
Unknown,
}
impl BlockContext {
pub fn name(&self) -> Option<&str> {
match self {
BlockContext::Action(name)
| BlockContext::Variable(name)
| BlockContext::Signer(name)
| BlockContext::Output(name)
| BlockContext::Flow(name)
| BlockContext::Addon(name) => Some(name),
BlockContext::Unknown => None,
}
}
pub fn block_type(&self) -> &str {
use crate::types::ConstructType;
match self {
BlockContext::Action(_) => ConstructType::Action.into(),
BlockContext::Variable(_) => ConstructType::Variable.into(),
BlockContext::Signer(_) => ConstructType::Signer.into(),
BlockContext::Output(_) => ConstructType::Output.into(),
BlockContext::Flow(_) => ConstructType::Flow.into(),
BlockContext::Addon(_) => ConstructType::Addon.into(),
BlockContext::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceType {
Input,
Variable,
Action,
Signer,
FlowInput,
Output,
}
#[derive(Debug, Clone)]
pub struct InputReference {
pub name: String,
pub full_path: String,
pub location: SourceLocation,
pub context: BlockContext,
pub reference_type: ReferenceType,
}
impl InputReference {
pub fn new(
name: String,
full_path: String,
location: SourceLocation,
context: BlockContext,
reference_type: ReferenceType,
) -> Self {
Self {
name,
full_path,
location,
context,
reference_type,
}
}
pub fn input(name: String, location: SourceLocation, context: BlockContext) -> Self {
let full_path = format!("input.{}", name);
Self::new(name, full_path, location, context, ReferenceType::Input)
}
pub fn variable(name: String, location: SourceLocation, context: BlockContext) -> Self {
let full_path = format!("var.{}", name);
Self::new(name, full_path, location, context, ReferenceType::Variable)
}
pub fn flow_input(name: String, location: SourceLocation, context: BlockContext) -> Self {
let full_path = format!("flow.{}", name);
Self::new(name, full_path, location, context, ReferenceType::FlowInput)
}
pub fn action(name: String, location: SourceLocation, context: BlockContext) -> Self {
let full_path = format!("action.{}", name);
Self::new(name, full_path, location, context, ReferenceType::Action)
}
pub fn signer(name: String, location: SourceLocation, context: BlockContext) -> Self {
let full_path = format!("signer.{}", name);
Self::new(name, full_path, location, context, ReferenceType::Signer)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_source_location_new() {
let loc = SourceLocation::new("test.tx".to_string(), 10, 5);
assert_eq!(loc.file, "test.tx");
assert_eq!(loc.line, 10);
assert_eq!(loc.column, 5);
}
#[test]
fn test_source_location_at_start() {
let loc = SourceLocation::at_start("test.tx".to_string());
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 1);
}
#[test]
fn test_source_mapper_simple() {
let source = "hello world";
let mapper = SourceMapper::new(source);
let (line, col) = mapper.span_to_position(&(0..5));
assert_eq!(line, 1);
assert_eq!(col, 1);
let (line, col) = mapper.span_to_position(&(6..11));
assert_eq!(line, 1);
assert_eq!(col, 7);
}
#[test]
fn test_source_mapper_multiline() {
let source = "line 1\nline 2\nline 3";
let mapper = SourceMapper::new(source);
let (line, col) = mapper.span_to_position(&(0..1));
assert_eq!(line, 1);
assert_eq!(col, 1);
let (line, col) = mapper.span_to_position(&(7..8));
assert_eq!(line, 2);
assert_eq!(col, 1);
let (line, col) = mapper.span_to_position(&(14..15));
assert_eq!(line, 3);
assert_eq!(col, 1);
}
#[test]
fn test_source_mapper_newline_boundary() {
let source = "abc\ndefg";
let mapper = SourceMapper::new(source);
let (line, col) = mapper.span_to_position(&(3..4));
assert_eq!(line, 1);
assert_eq!(col, 4);
let (line, col) = mapper.span_to_position(&(4..5));
assert_eq!(line, 2);
assert_eq!(col, 1);
}
#[test]
fn test_source_mapper_optional_none() {
let source = "test";
let mapper = SourceMapper::new(source);
let loc = mapper.optional_span_to_location(None, "test.tx".to_string());
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 1);
}
#[test]
fn test_block_context_name() {
use crate::types::ConstructType;
let ctx = BlockContext::Action("deploy".to_string());
assert_eq!(ctx.name(), Some("deploy"));
assert_eq!(ctx.block_type(), ConstructType::Action);
let ctx = BlockContext::Unknown;
assert_eq!(ctx.name(), None);
assert_eq!(ctx.block_type(), "unknown");
}
#[test]
fn test_input_reference_constructors() {
let loc = SourceLocation::new("test.tx".to_string(), 5, 10);
let ctx = BlockContext::Action("deploy".to_string());
let input_ref = InputReference::input("api_key".to_string(), loc.clone(), ctx.clone());
assert_eq!(input_ref.name, "api_key");
assert_eq!(input_ref.full_path, "input.api_key");
assert_eq!(input_ref.reference_type, ReferenceType::Input);
let var_ref = InputReference::variable("my_var".to_string(), loc.clone(), ctx.clone());
assert_eq!(var_ref.full_path, "var.my_var");
assert_eq!(var_ref.reference_type, ReferenceType::Variable);
let flow_ref = InputReference::flow_input("chain_id".to_string(), loc.clone(), ctx);
assert_eq!(flow_ref.full_path, "flow.chain_id");
assert_eq!(flow_ref.reference_type, ReferenceType::FlowInput);
}
#[test]
fn test_block_context_equality() {
let ctx1 = BlockContext::Action("deploy".to_string());
let ctx2 = BlockContext::Action("deploy".to_string());
let ctx3 = BlockContext::Action("other".to_string());
assert_eq!(ctx1, ctx2);
assert_ne!(ctx1, ctx3);
}
}