use std::ffi::c_void;
use std::os::raw::c_char;
use std::rc::Rc;
use sv_lang::kinds::{SyntaxKind, TokenKind, TriviaKind};
use sv_lang_sys as sys;
#[derive(Clone, Debug)]
pub struct Tok {
pub kind: TokenKind,
pub text: String,
pub file: Rc<str>,
pub line: usize,
pub col: usize,
pub missing: bool,
pub trivia: Vec<(TriviaKind, String)>,
}
#[derive(Clone, Debug)]
pub enum Elem {
Node(Node),
Tok(Tok),
List(Vec<Elem>),
Absent,
}
#[derive(Clone, Debug)]
pub struct Node {
pub kind: SyntaxKind,
pub members: Vec<Elem>,
}
impl Node {
pub fn node(&self, i: usize) -> Option<&Node> {
match self.members.get(i)? {
Elem::Node(n) => Some(n),
_ => None,
}
}
pub fn tok(&self, i: usize) -> Option<&Tok> {
match self.members.get(i)? {
Elem::Tok(t) if !t.missing => Some(t),
_ => None,
}
}
pub fn list(&self, i: usize) -> &[Elem] {
match self.members.get(i) {
Some(Elem::List(v)) => v,
_ => &[],
}
}
pub fn list_nodes(&self, i: usize) -> Vec<&Node> {
self.list(i)
.iter()
.filter_map(|e| match e {
Elem::Node(n) => Some(n),
_ => None,
})
.collect()
}
pub fn tokens(&self) -> Vec<&Tok> {
let mut out = Vec::new();
collect_tokens(&self.members, &mut out);
out
}
}
fn collect_tokens<'a>(elems: &'a [Elem], out: &mut Vec<&'a Tok>) {
for e in elems {
match e {
Elem::Node(n) => collect_tokens(&n.members, out),
Elem::Tok(t) => out.push(t),
Elem::List(v) => collect_tokens(v, out),
Elem::Absent => {}
}
}
}
enum Open {
Node(Node),
List(Vec<Elem>),
}
struct Sink {
sm: sys::slang_source_manager,
stack: Vec<Open>,
root: Option<Node>,
trivia: Vec<(TriviaKind, String)>,
files: Vec<Rc<str>>,
}
impl Sink {
fn push(&mut self, e: Elem) {
match self.stack.last_mut() {
Some(Open::Node(n)) => n.members.push(e),
Some(Open::List(v)) => v.push(e),
None => {
if let Elem::Node(n) = e {
self.root = Some(n);
}
}
}
}
fn file(&mut self, name: String) -> Rc<str> {
if let Some(f) = self.files.iter().find(|f| ***f == *name) {
return f.clone();
}
let f: Rc<str> = Rc::from(name);
self.files.push(f.clone());
f
}
}
unsafe fn sink<'a>(user: *mut c_void) -> &'a mut Sink {
unsafe { &mut *user.cast::<Sink>() }
}
unsafe fn text(ptr: *const c_char, len: usize) -> String {
if ptr.is_null() || len == 0 {
return String::new();
}
let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len) };
String::from_utf8_lossy(bytes).into_owned()
}
unsafe extern "C" fn on_start_node(user: *mut c_void, kind: u32, _s: u32) {
let s = unsafe { sink(user) };
s.stack.push(Open::Node(Node {
kind: SyntaxKind::from_raw(kind as u16).unwrap_or(SyntaxKind::Unknown),
members: Vec::new(),
}));
}
unsafe extern "C" fn on_finish_node(user: *mut c_void) {
let s = unsafe { sink(user) };
if let Some(Open::Node(n)) = s.stack.pop() {
s.push(Elem::Node(n));
}
}
unsafe extern "C" fn on_start_list(user: *mut c_void, _form: sys::slang_member_form) {
let s = unsafe { sink(user) };
s.stack.push(Open::List(Vec::new()));
}
unsafe extern "C" fn on_finish_list(user: *mut c_void) {
let s = unsafe { sink(user) };
if let Some(Open::List(v)) = s.stack.pop() {
s.push(Elem::List(v));
}
}
unsafe extern "C" fn on_absent(user: *mut c_void) {
let s = unsafe { sink(user) };
s.push(Elem::Absent);
}
unsafe extern "C" fn on_trivia(user: *mut c_void, kind: u32, t: *const c_char, len: usize) {
let (s, txt) = unsafe { (sink(user), text(t, len)) };
s.trivia.push((
TriviaKind::from_raw(kind as u8).unwrap_or(TriviaKind::Unknown),
txt,
));
}
unsafe extern "C" fn on_token(
user: *mut c_void,
kind: u32,
t: *const c_char,
len: usize,
loc: sys::slang_loc,
flags: u16,
) {
let (s, txt) = unsafe { (sink(user), text(t, len)) };
let at = unsafe {
if loc.buffer != 0 && sys::slang_source_manager_is_macro_loc(s.sm, loc) {
sys::slang_source_manager_original_loc(s.sm, loc)
} else {
loc
}
};
let (file, line, col) = if at.buffer == 0 {
(String::new(), 0, 0)
} else {
unsafe {
let f = sys::slang_source_manager_file_name(s.sm, at);
(
text(f.data, f.len),
sys::slang_source_manager_line(s.sm, at),
sys::slang_source_manager_column(s.sm, at),
)
}
};
let file = s.file(file);
let trivia = std::mem::take(&mut s.trivia);
s.push(Elem::Tok(Tok {
kind: TokenKind::from_raw(kind as u16).unwrap_or(TokenKind::Unknown),
text: txt,
file,
line,
col,
missing: flags & sys::SLANG_TOKEN_MISSING as u16 != 0,
trivia,
}));
}
pub fn parse(src: &str, path: &str, include_dirs: &[String]) -> Option<Node> {
let mut err = sys::slang_error::INIT;
unsafe {
let sm = sys::slang_source_manager_create(&mut err);
if sm.is_null() || err.status != sys::SLANG_OK {
return None;
}
for dir in include_dirs {
sys::slang_source_manager_add_include_dir(
sm,
dir.as_ptr().cast(),
dir.len(),
false,
&mut err,
);
if err.status != sys::SLANG_OK {
sys::slang_source_manager_destroy(sm);
return None;
}
}
let tree = sys::slang_syntax_tree_from_text(
sm,
src.as_ptr().cast(),
src.len(),
path.as_ptr().cast(),
path.len(),
path.as_ptr().cast(),
path.len(),
std::ptr::null_mut(),
&mut err,
);
let mut out = None;
if !tree.is_null() && err.status == sys::SLANG_OK {
let diags = sys::slang_syntax_tree_diagnostics(tree, &mut err);
let mut has_error = false;
if !diags.is_null() {
for i in 0..sys::slang_diagnostics_count(diags) {
let mut d: sys::slang_diag = std::mem::zeroed();
if sys::slang_diagnostics_at(diags, i, &mut d)
&& d.severity >= sys::SLANG_SEVERITY_ERROR
{
has_error = true;
}
}
sys::slang_diagnostics_destroy(diags);
}
if !has_error {
let mut s = Sink {
sm,
stack: Vec::new(),
root: None,
trivia: Vec::new(),
files: Vec::new(),
};
let cbs = sys::slang_syntax_sink {
start_node: Some(on_start_node),
trivia: Some(on_trivia),
token: Some(on_token),
absent: Some(on_absent),
start_list: Some(on_start_list),
finish_list: Some(on_finish_list),
finish_node: Some(on_finish_node),
};
sys::slang_syntax_tree_walk(tree, &cbs, (&mut s as *mut Sink).cast(), &mut err);
if err.status == sys::SLANG_OK {
out = s.root;
}
}
}
if !tree.is_null() {
sys::slang_syntax_tree_release(tree);
}
sys::slang_source_manager_destroy(sm);
out
}
}