Lines
98.25 %
Functions
89.83 %
Branches
100 %
use std::ops::{Add, AddAssign};
pub struct SourceFile<'s> {
source_text: &'s str,
}
impl<'s> SourceFile<'s> {
pub fn new(source_text: &'s str) -> SourceFile<'s> {
SourceFile { source_text }
pub fn get_text_snippet(&self, span: impl Into<Span>) -> &'s str {
let span = span.into();
let (start_idx, end_idx) = (span.start.to_usize(), span.end.to_usize());
&self.source_text[start_idx..end_idx]
// TODO(feroldi): Test this.
pub fn get_text(&self) -> &str {
self.source_text
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
impl<T> Spanned<T> {
pub fn new(value: T, span: Span) -> Spanned<T> {
Spanned { value, span }
pub fn with_dummy_span(value: T) -> Spanned<T> {
Spanned {
value,
span: Span::dummy(),
impl<T> std::ops::Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
impl<T> From<Spanned<T>> for Span {
fn from(spanned: Spanned<T>) -> Span {
spanned.span
/// This is an exclusive text range as in [start, end).
pub struct Span {
pub start: BytePos,
pub end: BytePos,
impl Span {
pub fn from_usizes(start: usize, end: usize) -> Span {
debug_assert!(start <= end, "cannot have start greater than end");
Span {
start: BytePos::from_usize(start),
end: BytePos::from_usize(end),
pub(crate) fn dummy() -> Span {
start: BytePos::from_usize(0),
end: BytePos::from_usize(0),
pub trait Pos: Sized + Add + AddAssign {
fn from_usize(value: usize) -> Self;
fn to_usize(self) -> usize;
pub struct BytePos(usize);
impl Pos for BytePos {
fn from_usize(value: usize) -> BytePos {
BytePos(value)
fn to_usize(self) -> usize {
self.0
impl Add for BytePos {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::from_usize(self.to_usize() + rhs.to_usize())
impl AddAssign for BytePos {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs
impl std::convert::From<usize> for BytePos {
fn from(value: usize) -> BytePos {
BytePos::from_usize(value)
#[allow(dead_code)]
fn calc_lines_positions(source_text: &str) -> Vec<BytePos> {
std::iter::once(0usize)
.chain(source_text.match_indices('\n').map(|(idx, _)| idx + 1))
.filter(|&pos| pos < source_text.len())
.map(BytePos)
.collect()
fn lookup_line_index(lines_pos: &[BytePos], pos: BytePos) -> Option<usize> {
lines_pos
.iter()
.rev()
.position(|lines_pos| lines_pos.0 <= pos.0)
.map(|line_index| lines_pos.len() - line_index - 1)
#[cfg(test)]
mod legacy_tests {
// These tests are here temporarily, because they don't have any use right
// now, so they aren't part of the API right now, but in the future,
// we'll have a diagnostics system that surely will make use of these
// functions, so we keep them here as is for now.
mod calc_lines_positions {
use crate::source_map::{calc_lines_positions, BytePos};
#[test]
fn empty_text() {
let source_text = "";
let source_lines_pos = calc_lines_positions(source_text);
assert_eq!(source_lines_pos, vec![]);
fn text_without_newline() {
let source_text = "some text without newline";
assert_eq!(source_lines_pos, vec![BytePos(0usize)]);
fn text_with_newline_at_the_end() {
let source_text = "abc\n";
fn text_with_newline_in_the_middle() {
let source_text = "abc\ndef";
assert_eq!(source_lines_pos, vec![BytePos(0usize), BytePos(4usize)]);
fn text_with_newline_at_the_start() {
let source_text = "\nabc";
assert_eq!(source_lines_pos, vec![BytePos(0usize), BytePos(1usize)]);
fn text_with_various_newlines_at_the_start() {
let source_text = "\n\n\nabc";
assert_eq!(
source_lines_pos,
vec![
BytePos(0usize),
BytePos(1usize),
BytePos(2usize),
BytePos(3usize),
]
);
fn text_with_various_newlines_at_the_end() {
let source_text = "abc\n\n\n";
vec![BytePos(0usize), BytePos(4usize), BytePos(5usize)]
mod lookup_line_index {
use crate::source_map::{lookup_line_index, BytePos};
fn empty_start_pos_of_lines() {
let start_pos_of_lines = Vec::<BytePos>::new();
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(0));
assert_eq!(line_index, None::<usize>);
fn one_line() {
let start_pos_of_lines = vec![BytePos(0)];
assert_eq!(line_index, Some(0usize));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(1));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(100));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(5000));
fn should_return_index_of_the_first_line_whose_pos_is_less_or_equal_to_input_pos() {
let start_pos_of_lines = vec![BytePos(0), BytePos(5), BytePos(10)];
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(4));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(5));
assert_eq!(line_index, Some(1usize));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(9));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(10));
assert_eq!(line_index, Some(2usize));
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(50));
fn contiguous_lines() {
let start_pos_of_lines = vec![BytePos(0), BytePos(1), BytePos(2)];
let line_index = lookup_line_index(&start_pos_of_lines, BytePos(2));