Skip to content

Commit

Permalink
Parser: Improve callable type parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ryangjchandler committed Dec 18, 2024
1 parent adfa389 commit a2b316a
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 29 deletions.
18 changes: 17 additions & 1 deletion crates/diagnostics/src/severity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::Display;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Severity {
Hint,
Information,
Expand All @@ -12,6 +12,22 @@ impl Severity {
pub fn with_ascii(&self) -> AsciiSeverity {
AsciiSeverity::new(self)
}

pub fn is_error(&self) -> bool {
self == &Severity::Error
}

pub fn is_warning(&self) -> bool {
self == &Severity::Warning
}

pub fn is_information(&self) -> bool {
self == &Severity::Information
}

pub fn is_hint(&self) -> bool {
self == &Severity::Hint
}
}

pub struct AsciiSeverity<'a> {
Expand Down
19 changes: 17 additions & 2 deletions crates/inference/src/generator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use pxp_ast::{visitor::Visitor, *};
use pxp_bytestring::ByteString;
use pxp_index::Index;
use pxp_type::Type;
use pxp_type::{CallableParameter, Type};
use visitor::{
walk_assignment_operation_expression, walk_backed_enum_statement, walk_class_statement,
walk_concrete_method, walk_expression, walk_function_statement, walk_method_call_expression,
Expand Down Expand Up @@ -429,7 +429,7 @@ impl Visitor for TypeMapGenerator<'_> {
fn bytestring_type(ty: &Type<Name>) -> Type<ByteString> {
match ty {
Type::Named(inner) => Type::Named(inner.symbol().clone()),
Type::NamedWithGenerics(inner, templates) => Type::NamedWithGenerics(
Type::Generic(inner, templates) => Type::Generic(
Box::new(bytestring_type(inner)),
templates.iter().map(bytestring_type).collect(),
),
Expand All @@ -448,11 +448,26 @@ fn bytestring_type(ty: &Type<Name>) -> Type<ByteString> {
Type::Array => Type::Array,
Type::Object => Type::Object,
Type::Mixed => Type::Mixed,
Type::CallableSignature(callable, parameters, return_type) => Type::CallableSignature(
Box::new(bytestring_type(callable)),
parameters
.iter()
.map(|p| CallableParameter {
r#type: bytestring_type(&p.r#type),
ellipsis: p.ellipsis,
ampersand: p.ampersand,
equal: p.equal,
name: p.name.clone(),
})
.collect(),
Box::new(bytestring_type(return_type)),
),
Type::Callable => Type::Callable,
Type::Iterable => Type::Iterable,
Type::StaticReference => Type::StaticReference,
Type::SelfReference => Type::SelfReference,
Type::ParentReference => Type::ParentReference,
Type::ArrayKey => Type::ArrayKey,
Type::TypedArray(key, value) => Type::TypedArray(
Box::new(bytestring_type(key)),
Box::new(bytestring_type(value)),
Expand Down
37 changes: 21 additions & 16 deletions crates/lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,14 @@ impl<'a> Lexer<'a> {
return self.docblock_eol();
}

match &self.source.read(2) {
match &self.source.read(3) {
[b'.', b'.', b'.', ..] => {
self.source.skip(3);

let span = self.source.span();

Token::new(TokenKind::Ellipsis, span, self.source.span_range(span))
},
[b'@', ident_start!(), ..] => {
self.source.skip(2);

Expand Down Expand Up @@ -369,15 +376,13 @@ impl<'a> Lexer<'a> {

Token::new(kind, span, self.source.span_range(span))
}
[b @ ident_start!(), ..] => {
[ident_start!(), ..] => {
self.source.next();
let mut qualified = false;
let mut last_was_slash = false;

let mut buffer = vec![*b];
while let Some(next @ ident!() | next @ b'\\') = self.source.current() {
if matches!(next, ident!()) {
buffer.push(*next);
self.source.next();
last_was_slash = false;
continue;
Expand All @@ -386,23 +391,30 @@ impl<'a> Lexer<'a> {
if *next == b'\\' && !last_was_slash {
qualified = true;
last_was_slash = true;
buffer.push(*next);

self.source.next();
continue;
}

break;
}

// Inside of a DocBlock, we can support non-standard identifiers such as `array-key`, `non-empty-string`, etc.
let (span, symbol) = match self.source.span_range(self.source.span()).as_ref() {
b"array" if self.source.read(4) == b"-key" => {
self.source.skip(4);

(self.source.span(), self.source.span_range(self.source.span()))
},
_ => (self.source.span(), self.source.span_range(self.source.span())),
};

let kind = if qualified {
TokenKind::QualifiedIdentifier
} else {
identifier_to_keyword(&buffer).unwrap_or(TokenKind::Identifier)
identifier_to_keyword(&symbol).unwrap_or(TokenKind::Identifier)
};

let span = self.source.span();
let symbol = self.source.span_range(span);

Token::new(kind, span, symbol)
}
[b'|', ..] => {
Expand Down Expand Up @@ -489,13 +501,6 @@ impl<'a> Lexer<'a> {

Token::new(TokenKind::GreaterThan, span, self.source.span_range(span))
}
[b'.', b'.', b'.', ..] => {
self.source.skip(3);

let span = self.source.span();

Token::new(TokenKind::Ellipsis, span, self.source.span_range(span))
}
[b'=', b'>', ..] => {
self.source.skip(2);

Expand Down
89 changes: 86 additions & 3 deletions crates/parser/src/internal/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use pxp_ast::*;
use pxp_diagnostics::Severity;
use pxp_span::Span;
use pxp_token::TokenKind;
use pxp_type::Type;
use pxp_type::{CallableParameter, Type};

impl<'a> Parser<'a> {
pub fn parse_data_type(&mut self) -> DataType {
Expand Down Expand Up @@ -232,7 +232,7 @@ impl<'a> Parser<'a> {

r#type
} else if current.kind == TokenKind::LeftParen {
todo!("parse docblock callable type");
self.parse_docblock_callable(r#type)
} else if current.kind == TokenKind::LeftBracket {
self.parse_docblock_array_or_offset_access(r#type)
} else {
Expand Down Expand Up @@ -278,7 +278,89 @@ impl<'a> Parser<'a> {
);
}

Type::NamedWithGenerics(Box::new(lhs), generic_types)
Type::Generic(Box::new(lhs), generic_types)
}

fn parse_docblock_callable(&mut self, lhs: Type<Name>) -> Type<Name> {
self.skip(TokenKind::LeftParen);
self.skip_doc_eol();

let mut parameters = vec![];

while self.current_kind() != TokenKind::RightParen {
parameters.push(self.parse_docblock_callable_parameter());

self.skip_doc_eol();

if self.current_kind() == TokenKind::Comma {
self.next();
}

self.skip_doc_eol();
}

self.skip(TokenKind::RightParen);

let return_type = if self.current_kind() == TokenKind::Colon {
self.next();
self.skip_doc_eol();

self.parse_docblock_type()
} else {
Type::Void
};

Type::CallableSignature(Box::new(lhs), parameters, Box::new(return_type))
}

fn parse_docblock_callable_parameter(&mut self) -> CallableParameter<Name> {
let r#type = self.parse_docblock_type();

self.skip_doc_eol();

let ampersand = if self.current_kind() == TokenKind::Ampersand {
Some(self.next())
} else {
None
};

self.skip_doc_eol();

let ellipsis = if self.current_kind() == TokenKind::Ellipsis {
Some(self.next())
} else {
None
};

self.skip_doc_eol();

let name = if self.current_kind() == TokenKind::Variable {
let name = self.current_symbol_as_bytestring();

self.next();

Some(name)
} else {
None
};

self.skip_doc_eol();

let equal = if self.current_kind() == TokenKind::Equals {
Some(self.next())
} else {
None
};

self.skip_doc_eol();

CallableParameter {
r#type,
ellipsis,
ampersand,
equal,
name,
}
}

fn parse_docblock_array_or_offset_access(&mut self, lhs: Type<Name>) -> Type<Name> {
Expand Down Expand Up @@ -430,6 +512,7 @@ impl<'a> Parser<'a> {
b"false" => Some(Type::False),
b"array" => Some(Type::Array),
b"callable" => Some(Type::Callable),
b"array-key" if parser.is_in_docblock() => Some(Type::ArrayKey),
_ => {
let id = parser.id();

Expand Down
2 changes: 1 addition & 1 deletion crates/type/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ rust-version.workspace = true
license-file.workspace = true

[dependencies]
pxp-bytestring = { version = "0.1.0", path = "../bytestring" }
pxp-span = { path = "../span" }
serde = { version = "1.0.193", features = ["derive"] }
45 changes: 40 additions & 5 deletions crates/type/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::fmt::{Debug, Display};

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, Default)]
use pxp_bytestring::ByteString;
use pxp_span::Span;

#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
pub enum Type<N: Debug + Display> {
Named(N),
NamedWithGenerics(Box<Type<N>>, Vec<Type<N>>),
Generic(Box<Type<N>>, Vec<Type<N>>),
Nullable(Box<Type<N>>),
Union(Vec<Type<N>>),
Intersection(Vec<Type<N>>),
Expand All @@ -24,15 +24,48 @@ pub enum Type<N: Debug + Display> {
#[default]
Mixed,
Callable,
CallableSignature(Box<Type<N>>, Vec<CallableParameter<N>>, Box<Type<N>>),
Iterable,
StaticReference,
SelfReference,
ParentReference,
ArrayKey,
TypedArray(Box<Type<N>>, Box<Type<N>>),
This,
Missing,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CallableParameter<N: Debug + Display> {
pub r#type: Type<N>,
pub ellipsis: Option<Span>,
pub ampersand: Option<Span>,
pub equal: Option<Span>,
pub name: Option<ByteString>,
}

impl<N: Debug + Display> Display for CallableParameter<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.r#type)?;

if self.ellipsis.is_some() {
write!(f, " ...")?;
} else if self.ampersand.is_some() {
write!(f, " &")?;
}

if let Some(name) = &self.name {
write!(f, " {}", name)?;
}

if self.equal.is_some() {
write!(f, "=")?;
}

Ok(())
}
}

impl<N: Debug + Display> Type<N> {
pub fn map<T: Debug + Display>(&self, cb: impl FnOnce(&Type<N>) -> Type<T>) -> Type<T> {
cb(self)
Expand Down Expand Up @@ -83,7 +116,7 @@ impl<N: Debug + Display> Display for Type<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Type::Named(inner) => write!(f, "{}", inner),
Type::NamedWithGenerics(inner, templates) => {
Type::Generic(inner, templates) => {
write!(
f,
"{}<{}>",
Expand Down Expand Up @@ -126,11 +159,13 @@ impl<N: Debug + Display> Display for Type<N> {
Type::Array => write!(f, "array"),
Type::Object => write!(f, "object"),
Type::Mixed => write!(f, "mixed"),
Type::CallableSignature(callable, parameters, return_type) => write!(f, "{}({}): {}", callable, parameters.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "), return_type),
Type::Callable => write!(f, "callable"),
Type::Iterable => write!(f, "iterable"),
Type::StaticReference => write!(f, "static"),
Type::SelfReference => write!(f, "self"),
Type::ParentReference => write!(f, "parent"),
Type::ArrayKey => write!(f, "array-key"),
Type::TypedArray(key, value) => write!(f, "array<{}, {}>", key, value),
Type::This => write!(f, "$this"),
Type::Missing => write!(f, "<missing>"),
Expand Down
13 changes: 12 additions & 1 deletion internal/bin/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,26 @@ fn main() {
let ast = Parser::parse(Lexer::new(&contents));

if !ast.diagnostics.is_empty() {
let mut has_error = false;

ast.diagnostics.iter().for_each(|error| {
has_error = error.severity.is_hint();

println!("{:?}", error);
println!(
" line: {}, column: {}",
error.span.start_line(&contents) + 1,
error.span.start_column(&contents) + 1
);
});

if stop_on_diagnostics {
if stop_on_diagnostics && has_error {
break;
}
}

drop(ast);

count += 1;
}

Expand Down

0 comments on commit a2b316a

Please sign in to comment.