use std::ops::Deref;
use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::ast::{self, Ast, JobList, Node};
use crate::parse_constants::{
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseKeyword,
ParseTokenType, ParseTreeFlags, SourceOffset, SourceRange, SOURCE_OFFSET_INVALID,
};
use crate::prelude::*;
use crate::tokenizer::TokenizerError;
use fish_common::{assert_send, assert_sync};
use fish_wcstringutil::count_newlines;
#[derive(Clone, Copy)]
pub struct ParseToken {
pub typ: ParseTokenType,
pub keyword: ParseKeyword,
pub has_dash_prefix: bool,
pub is_help_argument: bool,
pub is_newline: bool,
pub may_be_variable_assignment: bool,
pub tok_error: TokenizerError,
source_start: SourceOffset,
source_length: SourceOffset,
}
impl ParseToken {
pub fn new(typ: ParseTokenType) -> Self {
ParseToken {
typ,
keyword: ParseKeyword::None,
has_dash_prefix: false,
is_help_argument: false,
is_newline: false,
may_be_variable_assignment: false,
tok_error: TokenizerError::None,
source_start: SOURCE_OFFSET_INVALID.try_into().unwrap(),
source_length: 0,
}
}
pub fn set_source_start(&mut self, value: usize) {
self.source_start = value.try_into().unwrap();
}
pub fn source_start(&self) -> usize {
self.source_start.try_into().unwrap()
}
pub fn set_source_length(&mut self, value: usize) {
self.source_length = value.try_into().unwrap();
}
pub fn source_length(&self) -> usize {
self.source_length.try_into().unwrap()
}
pub fn range(&self) -> SourceRange {
SourceRange::new(self.source_start(), self.source_length())
}
pub fn is_dash_prefix_string(&self) -> bool {
self.typ == ParseTokenType::String && self.has_dash_prefix
}
pub fn describe(&self) -> WString {
let mut result = self.typ.to_wstr().to_owned();
if self.keyword != ParseKeyword::None {
sprintf!(=> &mut result, " <%s>", self.keyword.to_wstr());
}
result
}
pub fn user_presentable_description(&self) -> WString {
token_type_user_presentable_description(self.typ, self.keyword)
}
}
impl From<TokenizerError> for ParseErrorCode {
fn from(err: TokenizerError) -> Self {
match err {
TokenizerError::None => ParseErrorCode::None,
TokenizerError::UnterminatedQuote => ParseErrorCode::TokenizerUnterminatedQuote,
TokenizerError::UnterminatedSubshell => ParseErrorCode::TokenizerUnterminatedSubshell,
TokenizerError::UnterminatedSlice => ParseErrorCode::TokenizerUnterminatedSlice,
TokenizerError::UnterminatedEscape => ParseErrorCode::TokenizerUnterminatedEscape,
_ => ParseErrorCode::TokenizerOther,
}
}
}
pub struct ParsedSource {
pub src: WString,
pub ast: Ast,
}
unsafe impl Send for ParsedSource {}
unsafe impl Sync for ParsedSource {}
const _: () = assert_send::<ParsedSource>();
const _: () = assert_sync::<ParsedSource>();
#[derive(Default)]
pub struct SourceLineCache {
last_source: Option<NonNull<ParsedSource>>,
offset: usize,
count: usize,
}
impl ParsedSource {
pub fn lineno_for_offset(&self, offset: usize, cache: &mut SourceLineCache) -> u32 {
let self_ptr = unsafe { NonNull::new_unchecked(std::ptr::from_ref(self).cast_mut()) };
if cache.last_source != Some(self_ptr) {
cache.last_source = Some(self_ptr);
cache.offset = 0;
cache.count = 0;
}
#[allow(clippy::comparison_chain)] if offset > cache.offset {
cache.count += count_newlines(&self.src[cache.offset..offset]);
} else if offset < cache.offset {
cache.count -= count_newlines(&self.src[offset..cache.offset]);
}
cache.offset = offset;
cache.count as u32 + 1
}
}
impl ParsedSource {
pub fn new(src: WString, ast: Ast) -> Self {
ParsedSource { src, ast }
}
pub fn top_job_list(self: &Arc<Self>) -> NodeRef<JobList> {
NodeRef::new(Arc::clone(self), self.ast.top())
}
}
pub type ParsedSourceRef = Arc<ParsedSource>;
pub struct NodeRef<NodeType: Node> {
parsed_source: Pin<Arc<ParsedSource>>,
node: *const NodeType,
}
impl<NodeType: Node> NodeRef<NodeType> {
pub fn new(parsed_source: ParsedSourceRef, node: *const NodeType) -> Self {
NodeRef {
parsed_source: Pin::new(parsed_source),
node,
}
}
pub fn child_ref<ChildType: Node>(
&self,
func: impl FnOnce(&NodeType) -> &ChildType,
) -> NodeRef<ChildType> {
NodeRef {
parsed_source: self.parsed_source.clone(),
node: func(self),
}
}
pub fn source_offset(&self) -> Option<usize> {
self.try_source_range().map(|r| r.start())
}
pub fn source_str(&self) -> &wstr {
&self.parsed_source.src
}
}
impl<NodeType: Node> Clone for NodeRef<NodeType> {
fn clone(&self) -> Self {
NodeRef {
parsed_source: self.parsed_source.clone(),
node: self.node,
}
}
}
impl<NodeType: Node> Deref for NodeRef<NodeType> {
type Target = NodeType;
fn deref(&self) -> &Self::Target {
unsafe { &*self.node }
}
}
impl<NodeType: Node> NodeRef<NodeType> {
pub fn parsed_source(&self) -> &ParsedSource {
&self.parsed_source
}
pub fn parsed_source_ref(&self) -> ParsedSourceRef {
Pin::into_inner(self.parsed_source.clone())
}
pub fn lineno(&self) -> Option<std::num::NonZeroU32> {
self.lineno_with_cache(&mut SourceLineCache::default())
}
pub fn lineno_with_cache(&self, cache: &mut SourceLineCache) -> Option<std::num::NonZeroU32> {
let range = self.try_source_range()?;
let lineno = self.parsed_source.lineno_for_offset(range.start(), cache);
Some(std::num::NonZeroU32::new(lineno).unwrap())
}
}
unsafe impl<NodeType: Node> Send for NodeRef<NodeType> {}
unsafe impl<NodeType: Node> Sync for NodeRef<NodeType> {}
pub fn parse_source(
src: WString,
flags: ParseTreeFlags,
errors: Option<&mut ParseErrorList>,
) -> Option<ParsedSourceRef> {
let ast = ast::parse(&src, flags, errors);
if ast.errored() && !flags.continue_after_error {
None
} else {
Some(Arc::new(ParsedSource::new(src, ast)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lineno_for_offset() {
let src = L!("a\nb\nc\nd").to_owned();
let ps = ParsedSource::new(src, ast::parse(L!(""), ParseTreeFlags::default(), None));
let mut cache = SourceLineCache::default();
assert_eq!(ps.lineno_for_offset(0, &mut cache), 1);
assert_eq!(ps.lineno_for_offset(4, &mut cache), 3);
assert_eq!(ps.lineno_for_offset(6, &mut cache), 4);
assert_eq!(ps.lineno_for_offset(2, &mut cache), 2);
assert_eq!(ps.lineno_for_offset(0, &mut cache), 1);
}
}