use error_stack::Report;
use std::fs;
use std::io;
use std::path::Path;
use solar_parse::{
ast,
interface::{Session, Span},
Parser,
};
use super::{EvmParserError, EvmParserResult};
use crate::batbelt::evm::types::{EvmFile, EvmImport, ImportSymbol};
use super::contract_parser::parse_contract_definition;
pub fn parse_sol_file(file_path: &str) -> EvmParserResult<EvmFile> {
let source = fs::read_to_string(file_path).map_err(|e: io::Error| {
Report::new(EvmParserError).attach_printable(format!("Cannot read {}: {}", file_path, e))
})?;
if source.is_empty() {
return Err(Report::new(EvmParserError)
.attach_printable(format!("File is empty or unreadable: {}", file_path)));
}
let sess = Session::builder()
.with_buffer_emitter(solar_parse::interface::ColorChoice::Auto)
.build();
let result = sess.enter(|| -> Result<EvmFile, Report<EvmParserError>> {
let arena = ast::Arena::new();
let mut parser = Parser::from_file(&sess, &arena, Path::new(file_path)).map_err(|_| {
Report::new(EvmParserError).attach_printable(format!("Cannot open {}", file_path))
})?;
let ast = parser.parse_file().map_err(|e| {
e.emit();
Report::new(EvmParserError).attach_printable(format!("Parse error in {}", file_path))
})?;
let mut sol_file = EvmFile {
path: file_path.to_string(),
imports: Vec::new(),
contracts: Vec::new(),
pragma: None,
};
for item in ast.items.iter() {
match &item.kind {
ast::ItemKind::Pragma(pragma) => {
sol_file.pragma = Some(match &pragma.tokens {
ast::PragmaTokens::Version(name, req) => {
format!("{} {}", name.as_str(), req)
}
ast::PragmaTokens::Custom(name, value) => {
if let Some(val) = value {
format!("{} {}", name.as_str(), val.as_str())
} else {
name.as_str().to_string()
}
}
other => format!("{:?}", other),
});
}
ast::ItemKind::Import(import_dir) => {
let sol_import = parse_import(&sess, import_dir, item.span);
sol_file.imports.push(sol_import);
}
ast::ItemKind::Contract(contract_def) => {
let contract =
parse_contract_definition(&sess, contract_def, file_path, &source);
sol_file.contracts.push(contract);
}
_ => {}
}
}
Ok(sol_file)
});
result
}
fn parse_import(sess: &Session, import: &ast::ImportDirective<'_>, item_span: Span) -> EvmImport {
let path = import.path.value.as_str().to_string();
let line = span_to_line(sess, item_span);
match &import.items {
ast::ImportItems::Plain(alias) => EvmImport {
path,
symbols: if let Some(alias_ident) = alias {
vec![ImportSymbol {
name: "*".to_string(),
alias: Some(alias_ident.as_str().to_string()),
}]
} else {
vec![]
},
line,
},
ast::ImportItems::Aliases(aliases) => {
let symbols = aliases
.iter()
.map(|(ident, alias)| ImportSymbol {
name: ident.as_str().to_string(),
alias: alias.as_ref().map(|a| a.as_str().to_string()),
})
.collect();
EvmImport {
path,
symbols,
line,
}
}
ast::ImportItems::Glob(alias_ident) => EvmImport {
path,
symbols: vec![ImportSymbol {
name: "*".to_string(),
alias: Some(alias_ident.as_str().to_string()),
}],
line,
},
}
}
pub fn span_to_line(sess: &Session, span: Span) -> usize {
sess.source_map().lookup_char_pos(span.lo()).line
}
pub fn span_to_end_line(sess: &Session, span: Span) -> usize {
sess.source_map().lookup_char_pos(span.hi()).line
}
pub fn type_to_string(sess: &Session, ty: &ast::Type<'_>) -> String {
sess.source_map()
.span_to_snippet(ty.span)
.unwrap_or_else(|_| format!("{:?}", ty.kind))
}
pub fn extract_source_by_lines(source: &str, start_line: usize, end_line: usize) -> String {
let lines: Vec<&str> = source.lines().collect();
let start = if start_line > 0 { start_line - 1 } else { 0 };
let end = end_line.min(lines.len());
lines[start..end].join("\n")
}