foundry_compilers/resolver/
parse.rsuse foundry_compilers_core::utils;
use semver::VersionReq;
use solar_parse::{
ast,
interface::{sym, Pos},
};
use std::{
ops::Range,
path::{Path, PathBuf},
};
#[derive(Clone, Debug)]
pub struct SolData {
pub license: Option<Spanned<String>>,
pub version: Option<Spanned<String>>,
pub experimental: Option<Spanned<String>>,
pub imports: Vec<Spanned<SolImport>>,
pub version_req: Option<VersionReq>,
pub libraries: Vec<SolLibrary>,
pub contract_names: Vec<String>,
pub is_yul: bool,
pub parse_result: Result<(), String>,
}
impl SolData {
pub fn parse_result(&self) -> crate::Result<()> {
self.parse_result.clone().map_err(crate::SolcError::ParseError)
}
#[allow(dead_code)]
pub fn fmt_version<W: std::fmt::Write>(
&self,
f: &mut W,
) -> std::result::Result<(), std::fmt::Error> {
if let Some(version) = &self.version {
write!(f, "({})", version.data)?;
}
Ok(())
}
pub fn parse(content: &str, file: &Path) -> Self {
let is_yul = file.extension().map_or(false, |ext| ext == "yul");
let mut version = None;
let mut experimental = None;
let mut imports = Vec::<Spanned<SolImport>>::new();
let mut libraries = Vec::new();
let mut contract_names = Vec::new();
let mut parse_result = Ok(());
let sess = solar_parse::interface::Session::builder()
.with_buffer_emitter(Default::default())
.build();
sess.enter(|| {
let arena = ast::Arena::new();
let filename = solar_parse::interface::source_map::FileName::Real(file.to_path_buf());
let Ok(mut parser) =
solar_parse::Parser::from_source_code(&sess, &arena, filename, content.to_string())
else {
return;
};
let Ok(ast) = parser.parse_file().map_err(|e| e.emit()) else { return };
for item in ast.items {
let loc = item.span.lo().to_usize()..item.span.hi().to_usize();
match &item.kind {
ast::ItemKind::Pragma(pragma) => match &pragma.tokens {
ast::PragmaTokens::Version(name, req) if name.name == sym::solidity => {
version = Some(Spanned::new(req.to_string(), loc));
}
ast::PragmaTokens::Custom(name, value)
if name.as_str() == "experimental" =>
{
let value =
value.as_ref().map(|v| v.as_str().to_string()).unwrap_or_default();
experimental = Some(Spanned::new(value, loc));
}
_ => {}
},
ast::ItemKind::Import(import) => {
let path = import.path.value.to_string();
let aliases = match &import.items {
ast::ImportItems::Plain(None) | ast::ImportItems::Glob(None) => &[][..],
ast::ImportItems::Plain(Some(alias))
| ast::ImportItems::Glob(Some(alias)) => &[(*alias, None)][..],
ast::ImportItems::Aliases(aliases) => aliases,
};
let sol_import = SolImport::new(PathBuf::from(path)).set_aliases(
aliases
.iter()
.map(|(id, alias)| match alias {
Some(al) => SolImportAlias::Contract(
al.name.to_string(),
id.name.to_string(),
),
None => SolImportAlias::File(id.name.to_string()),
})
.collect(),
);
imports.push(Spanned::new(sol_import, loc));
}
ast::ItemKind::Contract(contract) => {
if contract.kind.is_library() {
libraries.push(SolLibrary { is_inlined: library_is_inlined(contract) });
}
contract_names.push(contract.name.to_string());
}
_ => {}
}
}
});
if let Err(e) = sess.emitted_diagnostics().unwrap() {
let e = e.to_string();
trace!("failed parsing {file:?}: {e}");
parse_result = Err(e);
}
let license = content.lines().next().and_then(|line| {
utils::capture_outer_and_inner(
line,
&utils::RE_SOL_SDPX_LICENSE_IDENTIFIER,
&["license"],
)
.first()
.map(|(cap, l)| Spanned::new(l.as_str().to_owned(), cap.range()))
});
let version_req = version.as_ref().and_then(|v| Self::parse_version_req(v.data()).ok());
Self {
version_req,
version,
experimental,
imports,
license,
libraries,
contract_names,
is_yul,
parse_result,
}
}
pub fn parse_version_req(version: &str) -> Result<VersionReq, semver::Error> {
let version = version.replace(' ', ",");
let exact = !matches!(&version[0..1], "*" | "^" | "=" | ">" | "<" | "~");
let mut version = VersionReq::parse(&version)?;
if exact {
version.comparators[0].op = semver::Op::Exact;
}
Ok(version)
}
}
#[derive(Clone, Debug)]
pub struct SolImport {
path: PathBuf,
aliases: Vec<SolImportAlias>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SolImportAlias {
File(String),
Contract(String, String),
}
impl SolImport {
pub fn new(path: PathBuf) -> Self {
Self { path, aliases: vec![] }
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn aliases(&self) -> &Vec<SolImportAlias> {
&self.aliases
}
fn set_aliases(mut self, aliases: Vec<SolImportAlias>) -> Self {
self.aliases = aliases;
self
}
}
#[derive(Clone, Debug)]
pub struct SolLibrary {
pub is_inlined: bool,
}
impl SolLibrary {
pub fn is_inlined(&self) -> bool {
self.is_inlined
}
}
#[derive(Clone, Debug)]
pub struct Spanned<T> {
pub span: Range<usize>,
pub data: T,
}
impl<T> Spanned<T> {
pub fn new(data: T, span: Range<usize>) -> Self {
Self { data, span }
}
pub fn data(&self) -> &T {
&self.data
}
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
pub fn loc_by_offset(&self, offset: isize) -> Range<usize> {
utils::range_by_offset(&self.span, offset)
}
}
fn library_is_inlined(contract: &ast::ItemContract<'_>) -> bool {
contract
.body
.iter()
.filter_map(|item| match &item.kind {
ast::ItemKind::Function(f) => Some(f),
_ => None,
})
.all(|f| {
!matches!(
f.header.visibility,
Some(ast::Visibility::Public | ast::Visibility::External)
)
})
}