use crate::ast::{Block, Local};
use crate::ast_names::{AstName, AstNameDenseHasher};
use crate::cst::CstNodeMap;
use crate::location::{Location, Position};
use luau_common::{BStr, BString, ByteSlice, DenseHashMap};
use std::fmt;
#[derive(Debug, Clone, Default)]
pub struct ParseOptions {
allow_declaration_syntax: bool,
capture_comments: bool,
no_error_limit: bool,
store_cst_data: bool,
}
#[derive(Debug, Clone)]
pub struct FragmentParseResumeSettings<'ast> {
pub local_map: DenseHashMap<AstName<'ast>, Option<&'ast Local<'ast>>, AstNameDenseHasher>,
pub local_stack: Vec<&'ast Local<'ast>>,
pub resume_position: Position,
}
impl ParseOptions {
pub fn allow_declaration_syntax(&self) -> bool {
self.allow_declaration_syntax
}
pub fn capture_comments(&self) -> bool {
self.capture_comments
}
pub fn no_error_limit(&self) -> bool {
self.no_error_limit
}
pub fn store_cst_data(&self) -> bool {
self.store_cst_data
}
pub fn with_declaration_syntax(mut self, allow: bool) -> Self {
self.allow_declaration_syntax = allow;
self
}
pub fn with_comment_capture(mut self, capture: bool) -> Self {
self.capture_comments = capture;
self
}
pub fn without_error_limit(mut self) -> Self {
self.no_error_limit = true;
self
}
pub fn with_cst_data(mut self, store: bool) -> Self {
self.store_cst_data = store;
self
}
}
#[derive(Debug, PartialEq)]
pub struct ParseMetadata<'ast> {
pub lines: usize,
pub hotcomments: Vec<HotComment>,
pub errors: Vec<ParseError>,
pub comment_locations: Vec<Comment>,
pub cst_nodes: CstNodeMap<'ast>,
}
impl<'ast> ParseMetadata<'ast> {
pub(crate) fn new(
lines: usize,
hotcomments: Vec<HotComment>,
errors: Vec<ParseError>,
comment_locations: Vec<Comment>,
cst_nodes: CstNodeMap<'ast>,
) -> Self {
Self {
lines,
hotcomments,
errors,
comment_locations,
cst_nodes,
}
}
pub fn mode(&self) -> Option<Mode> {
mode_from_hotcomments(&self.hotcomments)
}
pub fn compiler_directives(&self) -> Vec<CompileDirective> {
compiler_directives(&self.hotcomments)
}
}
#[derive(Debug, PartialEq)]
pub struct ParseResult<'ast> {
pub root: Block<'ast>,
pub metadata: ParseMetadata<'ast>,
}
impl<'ast> ParseResult<'ast> {
pub(crate) fn new(root: Block<'ast>, metadata: ParseMetadata<'ast>) -> Self {
Self { root, metadata }
}
pub fn is_within_comment(&self, position: Position) -> bool {
self.metadata
.comment_locations
.iter()
.any(|comment| comment.contains_position(position))
}
}
fn mode_from_hotcomments(hotcomments: &[HotComment]) -> Option<Mode> {
hotcomments.iter().find_map(|comment| {
if !comment.header {
return None;
}
match comment.content.as_slice() {
b"nocheck" => Some(Mode::NoCheck),
b"nonstrict" => Some(Mode::Nonstrict),
b"strict" => Some(Mode::Strict),
_ => None,
}
})
}
#[derive(Debug, PartialEq)]
pub struct ParseNodeResult<'ast, T> {
pub node: T,
pub metadata: ParseMetadata<'ast>,
}
impl<'ast, T> ParseNodeResult<'ast, T> {
pub(crate) fn new(node: T, metadata: ParseMetadata<'ast>) -> Self {
Self { node, metadata }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub location: Location,
pub message: ParseMessage,
}
impl ParseError {
pub fn new(location: Location, message: impl Into<ParseMessage>) -> Self {
Self {
location,
message: message.into(),
}
}
pub fn new_bytes(location: Location, message: Vec<u8>) -> Self {
Self {
location,
message: ParseMessage::from(message),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(formatter)
}
}
impl std::error::Error for ParseError {}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseErrors {
errors: Vec<ParseError>,
message: ParseMessage,
}
impl ParseErrors {
pub fn new(errors: Vec<ParseError>) -> Option<Self> {
if errors.is_empty() {
return None;
}
let message = parse_errors_message(&errors);
Some(Self { errors, message })
}
pub(crate) fn single(error: ParseError) -> Self {
Self {
message: error.message.clone(),
errors: vec![error],
}
}
pub fn first(&self) -> &ParseError {
&self.errors[0]
}
pub fn errors(&self) -> &[ParseError] {
&self.errors
}
pub fn message(&self) -> &ParseMessage {
&self.message
}
pub fn into_errors(self) -> Vec<ParseError> {
self.errors
}
}
impl std::ops::Deref for ParseErrors {
type Target = [ParseError];
fn deref(&self) -> &Self::Target {
&self.errors
}
}
impl fmt::Display for ParseErrors {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(formatter)
}
}
impl std::error::Error for ParseErrors {}
fn parse_errors_message(errors: &[ParseError]) -> ParseMessage {
match errors {
[] => ParseMessage::from(""),
[error] => error.message.clone(),
errors => errors
.iter()
.find(|error| {
error
.message
.starts_with("Exceeded allowed recursion depth;")
})
.map(|error| error.message.clone())
.unwrap_or_else(|| ParseMessage::from(format!("{} parse errors", errors.len()))),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseMessage(BString);
impl ParseMessage {
pub fn as_bstr(&self) -> &BStr {
self.0.as_bstr()
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn starts_with(&self, prefix: impl AsRef<[u8]>) -> bool {
self.as_bytes().starts_with(prefix.as_ref())
}
}
impl AsRef<[u8]> for ParseMessage {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl fmt::Display for ParseMessage {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0.as_bstr())
}
}
impl From<&str> for ParseMessage {
fn from(value: &str) -> Self {
Self(BString::from(value))
}
}
impl From<String> for ParseMessage {
fn from(value: String) -> Self {
Self(BString::from(value))
}
}
impl From<Vec<u8>> for ParseMessage {
fn from(bytes: Vec<u8>) -> Self {
Self(BString::new(bytes))
}
}
impl From<ParseMessage> for Vec<u8> {
fn from(value: ParseMessage) -> Self {
value.0.into()
}
}
impl From<ParseMessage> for BString {
fn from(value: ParseMessage) -> Self {
value.0
}
}
impl From<&ParseMessage> for BString {
fn from(value: &ParseMessage) -> Self {
value.0.clone()
}
}
impl PartialEq<&str> for ParseMessage {
fn eq(&self, other: &&str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<str> for ParseMessage {
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<[u8]> for ParseMessage {
fn eq(&self, other: &[u8]) -> bool {
self.as_bytes() == other
}
}
impl PartialEq<&[u8]> for ParseMessage {
fn eq(&self, other: &&[u8]) -> bool {
self.as_bytes() == *other
}
}
impl<const N: usize> PartialEq<&[u8; N]> for ParseMessage {
fn eq(&self, other: &&[u8; N]) -> bool {
self.as_bytes() == *other
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
NoCheck,
Nonstrict,
Strict,
Definition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompileDirective {
Native,
Optimize(u8),
}
#[derive(Debug, Clone, PartialEq)]
pub struct HotComment {
pub header: bool,
pub location: Location,
pub content: Vec<u8>,
}
pub(super) fn compiler_directives(hotcomments: &[HotComment]) -> Vec<CompileDirective> {
hotcomments
.iter()
.filter(|comment| comment.header)
.filter_map(|comment| match comment.content.as_slice() {
b"native" => Some(CompileDirective::Native),
content => content
.strip_prefix(b"optimize ")
.map(atoi_clamped_optimization_level)
.map(CompileDirective::Optimize),
})
.collect()
}
fn atoi_clamped_optimization_level(bytes: &[u8]) -> u8 {
let negative = bytes.first() == Some(&b'-');
let digits = bytes
.iter()
.skip(usize::from(matches!(bytes.first(), Some(b'-' | b'+'))))
.take_while(|byte| byte.is_ascii_digit())
.fold(0i32, |value, byte| {
value
.saturating_mul(10)
.saturating_add(i32::from(byte - b'0'))
});
digits
.checked_neg()
.filter(|_| negative)
.unwrap_or(digits)
.clamp(0, 2) as u8
}
#[derive(Debug, Clone, PartialEq)]
pub struct Comment {
pub kind: CommentKind,
pub location: Location,
}
impl Comment {
pub fn contains_position(&self, position: Position) -> bool {
self.location.contains(position)
|| (self.kind == CommentKind::Broken && self.location.begin <= position)
|| (self.kind == CommentKind::Line
&& self.location.end.line == position.line
&& self.location.begin <= position)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommentKind {
Line,
Block,
Broken,
}