Skip to content

Commit

Permalink
boa-dev#3780: add LinearPosition to Token & SourceText to Lexer
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita-str committed Sep 30, 2024
1 parent 628e31c commit c32509a
Show file tree
Hide file tree
Showing 24 changed files with 557 additions and 127 deletions.
4 changes: 3 additions & 1 deletion core/ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod module_item_list;
mod position;
mod punctuator;
mod source;
mod source_text;
mod statement_list;

pub mod declaration;
Expand All @@ -51,9 +52,10 @@ pub use self::{
expression::Expression,
keyword::Keyword,
module_item_list::{ModuleItem, ModuleItemList},
position::{Position, Span},
position::{LinearPosition, LinearSpan, Position, PositionGroup, Span},
punctuator::Punctuator,
source::{Module, Script},
source_text::SourceText,
statement::Statement,
statement_list::{StatementList, StatementListItem},
};
Expand Down
263 changes: 262 additions & 1 deletion core/ast/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,41 @@ impl fmt::Display for Position {
}
}

impl From<PositionGroup> for Position {
fn from(value: PositionGroup) -> Self {
value.pos
}
}

/// Linear position in the ECMAScript source code.
///
/// Stores linear position in the source code.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinearPosition {
pos: usize,
}

impl LinearPosition {
/// Creates a new `LinearPosition`.
#[inline]
#[must_use]
pub const fn new(pos: usize) -> Self {
Self { pos }
}
/// Gets the linear position.
#[inline]
#[must_use]
pub const fn pos(self) -> usize {
self.pos
}
}
impl fmt::Display for LinearPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.pos())
}
}

/// A span in the ECMAScript source code.
///
/// Stores a start position and an end position.
Expand Down Expand Up @@ -133,11 +168,130 @@ impl fmt::Display for Span {
}
}

/// A linear span in the ECMAScript source code.
///
/// Stores a linear start position and a linear end position.
///
/// Note that linear spans are of the form [start, end) i.e. that the
/// start position is inclusive and the end position is exclusive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LinearSpan {
start: LinearPosition,
end: LinearPosition,
}
impl LinearSpan {
/// Creates a new `LinearPosition`.
///
/// # Panics
///
/// Panics if the start position is bigger than the end position.
#[inline]
#[track_caller]
#[must_use]
pub const fn new(start: LinearPosition, end: LinearPosition) -> Self {
assert!(
start.pos <= end.pos,
"a linear span cannot start after its end"
);

Self { start, end }
}

/// Gets the starting position of the span.
#[inline]
#[must_use]
pub const fn start(self) -> LinearPosition {
self.start
}

/// Gets the final position of the span.
#[inline]
#[must_use]
pub const fn end(self) -> LinearPosition {
self.end
}

/// Checks if this span inclusively contains another span or position.
pub fn contains<S>(self, other: S) -> bool
where
S: Into<Self>,
{
let other = other.into();
self.start <= other.start && self.end >= other.end
}
}

impl From<LinearPosition> for LinearSpan {
fn from(pos: LinearPosition) -> Self {
Self {
start: pos,
end: pos,
}
}
}

impl PartialOrd for LinearSpan {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
Some(Ordering::Equal)
} else if self.end < other.start {
Some(Ordering::Less)
} else if self.start > other.end {
Some(Ordering::Greater)
} else {
None
}
}
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// A position group of `LinearPosition` and `Position` related to the same position in the ECMAScript source code.
pub struct PositionGroup {
pos: Position,
linear_pos: LinearPosition,
}
impl PositionGroup {
/// Creates a new `PositionGroup`.
#[inline]
#[must_use]
pub const fn new(pos: Position, linear_pos: LinearPosition) -> Self {
Self { pos, linear_pos }
}
/// Get the `Position`.
#[inline]
#[must_use]
pub fn position(&self) -> Position {
self.pos
}
/// Get the `LinearPosition`.
#[inline]
#[must_use]
pub fn linear_position(&self) -> LinearPosition {
self.linear_pos
}

/// Gets the line number of the position.
#[inline]
#[must_use]
pub const fn line_number(&self) -> u32 {
self.pos.line_number()
}

/// Gets the column number of the position.
#[inline]
#[must_use]
pub const fn column_number(&self) -> u32 {
self.pos.column_number()
}
}

#[cfg(test)]
mod tests {
#![allow(clippy::similar_names)]
#![allow(unused_must_use)]
use super::{Position, Span};
use super::{LinearPosition, LinearSpan, Position, Span};

/// Checks that we cannot create a position with 0 as the column.
#[test]
Expand All @@ -162,6 +316,13 @@ mod tests {
assert_ne!(Position::new(10, 50), Position::new(11, 51));
}

/// Checks that the `PartialEq` implementation of `LinearPosition` is consistent.
#[test]
fn linear_position_equality() {
assert_eq!(LinearPosition::new(1050), LinearPosition::new(1050));
assert_ne!(LinearPosition::new(1050), LinearPosition::new(1051));
}

/// Checks that the `PartialOrd` implementation of `Position` is consistent.
#[test]
fn position_order() {
Expand All @@ -176,6 +337,13 @@ mod tests {
assert!(Position::new(11, 49) > Position::new(10, 50));
}

/// Checks that the `PartialOrd` implementation of `LinearPosition` is consistent.
#[test]
fn linear_position_order() {
assert!(LinearPosition::new(1050) < LinearPosition::new(1051));
assert!(LinearPosition::new(1149) > LinearPosition::new(1050));
}

/// Checks that the position getters actually retrieve correct values.
#[test]
fn position_getters() {
Expand All @@ -202,6 +370,15 @@ mod tests {
Span::new(b, a);
}

/// Checks that we cannot create an invalid linear span.
#[test]
#[should_panic(expected = "a linear span cannot start after its end")]
fn invalid_linear_span() {
let a = LinearPosition::new(1030);
let b = LinearPosition::new(1050);
LinearSpan::new(b, a);
}

/// Checks that we can create valid spans.
#[test]
fn span_creation() {
Expand All @@ -213,6 +390,17 @@ mod tests {
Span::from(a);
}

/// Checks that we can create valid linear spans.
#[test]
fn linear_span_creation() {
let a = LinearPosition::new(1030);
let b = LinearPosition::new(1050);

LinearSpan::new(a, b);
let span_aa = LinearSpan::new(a, a);
assert_eq!(LinearSpan::from(a), span_aa);
}

/// Checks that the `PartialEq` implementation of `Span` is consistent.
#[test]
fn span_equality() {
Expand All @@ -236,6 +424,24 @@ mod tests {
assert_eq!(span_a, span_aa);
}

/// Checks that the `PartialEq` implementation of `LinearSpan` is consistent.
#[test]
fn linear_span_equality() {
let a = LinearPosition::new(1030);
let b = LinearPosition::new(1050);
let c = LinearPosition::new(1150);

let span_ab = LinearSpan::new(a, b);
let span_ab_2 = LinearSpan::new(a, b);
let span_ac = LinearSpan::new(a, c);
let span_bc = LinearSpan::new(b, c);

assert_eq!(span_ab, span_ab_2);
assert_ne!(span_ab, span_ac);
assert_ne!(span_ab, span_bc);
assert_ne!(span_bc, span_ac);
}

/// Checks that the getters retrieve the correct value.
#[test]
fn span_getters() {
Expand Down Expand Up @@ -278,6 +484,36 @@ mod tests {
assert!(!span_bd.contains(span_ac));
}

/// Checks that the `LinearSpan::contains()` method works properly.
#[test]
fn linear_span_contains() {
let a = LinearPosition::new(1050);
let b = LinearPosition::new(1080);
let c = LinearPosition::new(1120);
let d = LinearPosition::new(1125);

let span_ac = LinearSpan::new(a, c);
assert!(span_ac.contains(b));

let span_ab = LinearSpan::new(a, b);
let span_cd = LinearSpan::new(c, d);

assert!(!span_ab.contains(span_cd));
assert!(span_ab.contains(b));

let span_ad = LinearSpan::new(a, d);
let span_bc = LinearSpan::new(b, c);

assert!(span_ad.contains(span_bc));
assert!(!span_bc.contains(span_ad));

let span_ac = LinearSpan::new(a, c);
let span_bd = LinearSpan::new(b, d);

assert!(!span_ac.contains(span_bd));
assert!(!span_bd.contains(span_ac));
}

/// Checks that the string representation of a span is correct.
#[test]
fn span_to_string() {
Expand All @@ -303,4 +539,29 @@ mod tests {
assert!(span_ab < span_cd);
assert!(span_cd > span_ab);
}

/// Checks that the ordering of linear spans is correct.
#[test]
fn linear_span_ordering() {
let a = LinearPosition::new(1050);
let b = LinearPosition::new(1052);
let c = LinearPosition::new(1120);
let d = LinearPosition::new(1125);

let span_ab = LinearSpan::new(a, b);
let span_cd = LinearSpan::new(c, d);

let span_ac = LinearSpan::new(a, c);
let span_bd = LinearSpan::new(b, d);

assert!(span_ab < span_cd);
assert!(span_cd > span_ab);
assert_eq!(span_bd.partial_cmp(&span_ac), None);
assert_eq!(span_ac.partial_cmp(&span_bd), None);
}
}

// TODO: union Span & LinearSpan into `SpanBase<T>` and then:
// * Span = SpanBase<Position>;
// * LinearSpan = SpanBase<LinearPosition>;
// ?
Loading

0 comments on commit c32509a

Please sign in to comment.