From 8207c31aec6336b773fbf4661fdb87625c8b584e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 10 Aug 2021 11:28:12 +0200 Subject: [PATCH] Minor refactorings - Reorder parser methods and use `Pos` everywhere - Remove tab special handling for columns and adapt heading/list/enum indent handling - Don't panic when a file has an empty path --- src/eval/mod.rs | 21 +-- src/library/elements.rs | 2 +- src/parse/mod.rs | 70 +++---- src/parse/parser.rs | 343 +++++++++++++++++------------------ src/source.rs | 114 ++++++------ tests/ref/markup/lists.png | Bin 14750 -> 15207 bytes tests/typ/markup/enums.typ | 2 +- tests/typ/markup/heading.typ | 2 +- tests/typ/markup/lists.typ | 32 ++-- 9 files changed, 292 insertions(+), 294 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 22c7c0b43..e911596df 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -23,7 +23,7 @@ pub use value::*; use std::collections::HashMap; use std::io; use std::mem; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::rc::Rc; use crate::diag::{Error, StrResult, Tracepoint, TypResult}; @@ -107,7 +107,7 @@ impl<'a> EvalContext<'a> { /// Process an import of a module relative to the current location. pub fn import(&mut self, path: &str, span: Span) -> TypResult { // Load the source file. - let full = self.relpath(path); + let full = self.make_path(path); let id = self.sources.load(&full).map_err(|err| { Error::boxed(self.source, span, match err.kind() { io::ErrorKind::NotFound => "file not found".into(), @@ -157,15 +157,14 @@ impl<'a> EvalContext<'a> { Ok(id) } - /// Complete a path that is relative to the current file to be relative to - /// the environment's current directory. - pub fn relpath(&self, path: impl AsRef) -> PathBuf { - self.sources - .get(self.source) - .path() - .parent() - .expect("is a file") - .join(path) + /// Complete a user-entered path (relative to the source file) to be + /// relative to the compilation environment's root. + pub fn make_path(&self, path: &str) -> PathBuf { + if let Some(dir) = self.sources.get(self.source).path().parent() { + dir.join(path) + } else { + path.into() + } } } diff --git a/src/library/elements.rs b/src/library/elements.rs index e021c0c63..98165204d 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -15,7 +15,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult { let width = args.named("width")?; let height = args.named("height")?; - let full = ctx.relpath(path.v.as_str()); + let full = ctx.make_path(&path.v); let id = ctx.images.load(&full).map_err(|err| { Error::boxed(args.source, path.span, match err.kind() { io::ErrorKind::NotFound => "file not found".into(), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index f033e01f2..9eabcfc75 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -34,18 +34,16 @@ fn tree(p: &mut Parser) -> SyntaxTree { tree_while(p, true, &mut |_| true) } -/// Parse a syntax tree that stays right of the column at the start of the next -/// non-whitespace token. -fn tree_indented(p: &mut Parser) -> SyntaxTree { +/// Parse a syntax tree that stays right of the given column. +fn tree_indented(p: &mut Parser, column: usize) -> SyntaxTree { p.eat_while(|t| match t { Token::Space(n) => n == 0, Token::LineComment(_) | Token::BlockComment(_) => true, _ => false, }); - let column = p.column(p.next_start()); tree_while(p, false, &mut |p| match p.peek() { - Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) >= column, + Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) > column, _ => true, }) } @@ -68,7 +66,7 @@ where let start = p.next_start(); let tree = tree_while(p, true, f); call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr { - span: p.span(start), + span: p.span_from(start), tree: Rc::new(tree), }))); } @@ -189,7 +187,8 @@ fn raw(p: &mut Parser, token: RawToken) -> SyntaxNode { /// Parse a heading. fn heading(p: &mut Parser) -> SyntaxNode { let start = p.next_start(); - p.assert(Token::Eq); + let column = p.column(start); + p.eat_assert(Token::Eq); // Count depth. let mut level: usize = 1; @@ -198,28 +197,29 @@ fn heading(p: &mut Parser) -> SyntaxNode { } if level > 6 { - return SyntaxNode::Text(p.eaten_from(start).into()); + return SyntaxNode::Text(p.get(start .. p.prev_end()).into()); } - let body = tree_indented(p); - - SyntaxNode::Heading(HeadingNode { span: p.span(start), level, body }) + let body = tree_indented(p, column); + SyntaxNode::Heading(HeadingNode { span: p.span_from(start), level, body }) } /// Parse a single list item. fn list_item(p: &mut Parser) -> SyntaxNode { let start = p.next_start(); - p.assert(Token::Hyph); - let body = tree_indented(p); - SyntaxNode::List(ListItem { span: p.span(start), body }) + let column = p.column(start); + p.eat_assert(Token::Hyph); + let body = tree_indented(p, column); + SyntaxNode::List(ListItem { span: p.span_from(start), body }) } /// Parse a single enum item. fn enum_item(p: &mut Parser, number: Option) -> SyntaxNode { let start = p.next_start(); - p.assert(Token::Numbering(number)); - let body = tree_indented(p); - SyntaxNode::Enum(EnumItem { span: p.span(start), number, body }) + let column = p.column(start); + p.eat_assert(Token::Numbering(number)); + let body = tree_indented(p, column); + SyntaxNode::Enum(EnumItem { span: p.span_from(start), number, body }) } /// Parse an expression. @@ -240,7 +240,7 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option { Some(op) => { let prec = op.precedence(); let expr = Box::new(expr_with(p, atomic, prec)?); - Expr::Unary(UnaryExpr { span: p.span(start), op, expr }) + Expr::Unary(UnaryExpr { span: p.span_from(start), op, expr }) } None => primary(p, atomic)?, }; @@ -529,7 +529,7 @@ fn block(p: &mut Parser, scoping: bool) -> Expr { fn call(p: &mut Parser, callee: Expr) -> Option { let mut wide = p.eat_if(Token::Excl); if wide && p.outer_mode() == TokenMode::Code { - let span = p.span(callee.span().start); + let span = p.span_from(callee.span().start); p.error(span, "wide calls are only allowed directly in templates"); wide = false; } @@ -552,7 +552,7 @@ fn call(p: &mut Parser, callee: Expr) -> Option { } Some(Expr::Call(CallExpr { - span: p.span(callee.span().start), + span: p.span_from(callee.span().start), callee: Box::new(callee), wide, args, @@ -571,7 +571,7 @@ fn args(p: &mut Parser) -> CallArgs { fn with_expr(p: &mut Parser, callee: Expr) -> Option { if p.peek() == Some(Token::LeftParen) { Some(Expr::With(WithExpr { - span: p.span(callee.span().start), + span: p.span_from(callee.span().start), callee: Box::new(callee), args: args(p), })) @@ -584,7 +584,7 @@ fn with_expr(p: &mut Parser, callee: Expr) -> Option { /// Parse a let expression. fn let_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::Let); + p.eat_assert(Token::Let); let mut let_expr = None; if let Some(binding) = ident(p) { @@ -622,7 +622,7 @@ fn let_expr(p: &mut Parser) -> Option { } let_expr = Some(Expr::Let(LetExpr { - span: p.span(start), + span: p.span_from(start), binding, init: init.map(Box::new), })); @@ -634,7 +634,7 @@ fn let_expr(p: &mut Parser) -> Option { /// Parse an if expresion. fn if_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::If); + p.eat_assert(Token::If); let mut if_expr = None; if let Some(condition) = expr(p) { @@ -650,7 +650,7 @@ fn if_expr(p: &mut Parser) -> Option { } if_expr = Some(Expr::If(IfExpr { - span: p.span(start), + span: p.span_from(start), condition: Box::new(condition), if_body: Box::new(if_body), else_body: else_body.map(Box::new), @@ -664,13 +664,13 @@ fn if_expr(p: &mut Parser) -> Option { /// Parse a while expresion. fn while_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::While); + p.eat_assert(Token::While); let mut while_expr = None; if let Some(condition) = expr(p) { if let Some(body) = body(p) { while_expr = Some(Expr::While(WhileExpr { - span: p.span(start), + span: p.span_from(start), condition: Box::new(condition), body: Box::new(body), })); @@ -683,15 +683,15 @@ fn while_expr(p: &mut Parser) -> Option { /// Parse a for expression. fn for_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::For); + p.eat_assert(Token::For); let mut for_expr = None; if let Some(pattern) = for_pattern(p) { - if p.expect(Token::In) { + if p.eat_expect(Token::In) { if let Some(iter) = expr(p) { if let Some(body) = body(p) { for_expr = Some(Expr::For(ForExpr { - span: p.span(start), + span: p.span_from(start), pattern, iter: Box::new(iter), body: Box::new(body), @@ -718,7 +718,7 @@ fn for_pattern(p: &mut Parser) -> Option { /// Parse an import expression. fn import_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::Import); + p.eat_assert(Token::Import); let imports = if p.eat_if(Token::Star) { // This is the wildcard scenario. @@ -735,10 +735,10 @@ fn import_expr(p: &mut Parser) -> Option { }; let mut import_expr = None; - if p.expect(Token::From) { + if p.eat_expect(Token::From) { if let Some(path) = expr(p) { import_expr = Some(Expr::Import(ImportExpr { - span: p.span(start), + span: p.span_from(start), imports, path: Box::new(path), })); @@ -751,11 +751,11 @@ fn import_expr(p: &mut Parser) -> Option { /// Parse an include expression. fn include_expr(p: &mut Parser) -> Option { let start = p.next_start(); - p.assert(Token::Include); + p.eat_assert(Token::Include); expr(p).map(|path| { Expr::Include(IncludeExpr { - span: p.span(start), + span: p.span_from(start), path: Box::new(path), }) }) diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 326fc280d..4f2e59c60 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1,5 +1,4 @@ use std::fmt::{self, Debug, Formatter}; -use std::ops::Range; use super::{TokenMode, Tokens}; use crate::diag::Error; @@ -22,9 +21,9 @@ pub struct Parser<'s> { /// (Same as `next` except if we are at the end of group, then `None`). peeked: Option>, /// The end position of the last (non-whitespace if in code mode) token. - prev_end: usize, + prev_end: Pos, /// The start position of the peeked token. - next_start: usize, + next_start: Pos, } /// A logical group of tokens, e.g. `[...]`. @@ -32,7 +31,7 @@ pub struct Parser<'s> { struct GroupEntry { /// The start position of the group. Used by `Parser::end_group` to return /// The group's full span. - pub start: usize, + pub start: Pos, /// The kind of group this is. This decides which tokens will end the group. /// For example, a [`Group::Paren`] will be ended by /// [`Token::RightParen`]. @@ -70,8 +69,8 @@ impl<'s> Parser<'s> { groups: vec![], next, peeked: next, - prev_end: 0, - next_start: 0, + prev_end: Pos::ZERO, + next_start: Pos::ZERO, } } @@ -80,37 +79,144 @@ impl<'s> Parser<'s> { self.errors } - /// Add an error with location and message. - pub fn error(&mut self, span: impl Into, message: impl Into) { - self.errors.push(Error::new(self.source.id(), span, message)); + /// Whether the end of the source string or group is reached. + pub fn eof(&self) -> bool { + self.peek().is_none() } - /// Eat the next token and add an error that it is not the expected `thing`. - pub fn expected(&mut self, what: &str) { - let before = self.next_start(); - if let Some(found) = self.eat() { - let after = self.prev_end(); - self.error( - before .. after, - format!("expected {}, found {}", what, found.name()), - ); + /// Consume the next token. + pub fn eat(&mut self) -> Option> { + let token = self.peek()?; + self.bump(); + Some(token) + } + + /// Eat the next token and return its source range. + pub fn eat_span(&mut self) -> Span { + let start = self.next_start(); + self.eat(); + Span::new(start, self.prev_end()) + } + + /// Consume the next token if it is the given one. + pub fn eat_if(&mut self, t: Token) -> bool { + if self.peek() == Some(t) { + self.bump(); + true } else { - self.expected_at(self.next_start(), what); + false } } - /// Add an error that `what` was expected at the given position. - pub fn expected_at(&mut self, pos: impl Into, what: &str) { - self.error(pos.into(), format!("expected {}", what)); + /// Consume the next token if the closure maps it a to `Some`-variant. + pub fn eat_map(&mut self, f: F) -> Option + where + F: FnOnce(Token<'s>) -> Option, + { + let token = self.peek()?; + let mapped = f(token); + if mapped.is_some() { + self.bump(); + } + mapped } - /// Eat the next token and add an error that it is unexpected. - pub fn unexpected(&mut self) { - let before = self.next_start(); - if let Some(found) = self.eat() { - let after = self.prev_end(); - self.error(before .. after, format!("unexpected {}", found.name())); + /// Consume the next token if it is the given one and produce an error if + /// not. + pub fn eat_expect(&mut self, t: Token) -> bool { + let eaten = self.eat_if(t); + if !eaten { + self.expected_at(self.prev_end(), t.name()); } + eaten + } + + /// Consume the next token, debug-asserting that it is one of the given ones. + pub fn eat_assert(&mut self, t: Token) { + let next = self.eat(); + debug_assert_eq!(next, Some(t)); + } + + /// Consume tokens while the condition is true. + pub fn eat_while(&mut self, mut f: F) + where + F: FnMut(Token<'s>) -> bool, + { + while self.peek().map_or(false, |t| f(t)) { + self.eat(); + } + } + + /// Peek at the next token without consuming it. + pub fn peek(&self) -> Option> { + self.peeked + } + + /// Peek at the next token if it follows immediately after the last one + /// without any whitespace in between. + pub fn peek_direct(&self) -> Option> { + if self.next_start() == self.prev_end() { + self.peeked + } else { + None + } + } + + /// Peek at the span of the next token. + /// + /// Has length zero if `peek()` returns `None`. + pub fn peek_span(&self) -> Span { + Span::new(self.next_start(), self.next_end()) + } + + /// Peek at the source of the next token. + pub fn peek_src(&self) -> &'s str { + self.get(self.peek_span()) + } + + /// Checks whether the next token fulfills a condition. + /// + /// Returns `false` if there is no next token. + pub fn check(&self, f: F) -> bool + where + F: FnOnce(Token<'s>) -> bool, + { + self.peek().map_or(false, f) + } + + /// The byte position at which the last token ended. + /// + /// Refers to the end of the last _non-whitespace_ token in code mode. + pub fn prev_end(&self) -> Pos { + self.prev_end.into() + } + + /// The byte position at which the next token starts. + pub fn next_start(&self) -> Pos { + self.next_start.into() + } + + /// The byte position at which the next token will end. + /// + /// Is the same as [`next_start()`][Self::next_start] if `peek()` returns + /// `None`. + pub fn next_end(&self) -> Pos { + self.tokens.index().into() + } + + /// The span from `start` to [`self.prev_end()`](Self::prev_end). + pub fn span_from(&self, start: Pos) -> Span { + Span::new(start, self.prev_end()) + } + + /// Determine the column index for the given byte position. + pub fn column(&self, pos: Pos) -> usize { + self.source.pos_to_column(pos).unwrap() + } + + /// Slice out part of the source string. + pub fn get(&self, span: impl Into) -> &'s str { + self.tokens.scanner().get(span.into().to_range()) } /// Continue parsing in a group. @@ -131,9 +237,9 @@ impl<'s> Parser<'s> { self.repeek(); match kind { - Group::Paren => self.assert(Token::LeftParen), - Group::Bracket => self.assert(Token::LeftBracket), - Group::Brace => self.assert(Token::LeftBrace), + Group::Paren => self.eat_assert(Token::LeftParen), + Group::Bracket => self.eat_assert(Token::LeftBracket), + Group::Brace => self.eat_assert(Token::LeftBrace), Group::Stmt => {} Group::Expr => {} Group::Imports => {} @@ -171,7 +277,8 @@ impl<'s> Parser<'s> { // Rescan the peeked token if the mode changed. if rescan { - self.jump(self.prev_end()); + self.tokens.jump(self.prev_end().to_usize()); + self.bump(); } Span::new(group.start, self.prev_end()) @@ -188,163 +295,43 @@ impl<'s> Parser<'s> { self.groups.last().map_or(TokenMode::Markup, |group| group.outer_mode) } - /// Whether the end of the source string or group is reached. - pub fn eof(&self) -> bool { - self.peek().is_none() + /// Add an error with location and message. + pub fn error(&mut self, span: impl Into, message: impl Into) { + self.errors.push(Error::new(self.source.id(), span, message)); } - /// Peek at the next token without consuming it. - pub fn peek(&self) -> Option> { - self.peeked - } - - /// Peek at the next token if it follows immediately after the last one - /// without any whitespace in between. - pub fn peek_direct(&self) -> Option> { - if self.next_start() == self.prev_end() { - self.peeked + /// Eat the next token and add an error that it is not the expected `thing`. + pub fn expected(&mut self, what: &str) { + let before = self.next_start(); + if let Some(found) = self.eat() { + let after = self.prev_end(); + self.error( + before .. after, + format!("expected {}, found {}", what, found.name()), + ); } else { - None + self.expected_at(self.next_start(), what); } } - /// Peek at the span of the next token. - /// - /// Has length zero if `peek()` returns `None`. - pub fn peek_span(&self) -> Span { - self.peek_range().into() + /// Add an error that `what` was expected at the given position. + pub fn expected_at(&mut self, pos: Pos, what: &str) { + self.error(pos, format!("expected {}", what)); } - /// Peek at the source of the next token. - pub fn peek_src(&self) -> &'s str { - self.tokens.scanner().get(self.peek_range()) - } - - /// Peek at the source range (start and end index) of the next token. - pub fn peek_range(&self) -> Range { - self.next_start() .. self.next_end() - } - - /// Checks whether the next token fulfills a condition. - /// - /// Returns `false` if there is no next token. - pub fn check(&self, f: F) -> bool - where - F: FnOnce(Token<'s>) -> bool, - { - self.peek().map_or(false, f) - } - - /// Consume the next token. - pub fn eat(&mut self) -> Option> { - let token = self.peek()?; - self.bump(); - Some(token) - } - - /// Consume the next token if it is the given one. - pub fn eat_if(&mut self, t: Token) -> bool { - if self.peek() == Some(t) { - self.bump(); - true - } else { - false + /// Eat the next token and add an error that it is unexpected. + pub fn unexpected(&mut self) { + let before = self.next_start(); + if let Some(found) = self.eat() { + let after = self.prev_end(); + self.error(before .. after, format!("unexpected {}", found.name())); } } - /// Consume tokens while the condition is true. - pub fn eat_while(&mut self, mut f: F) - where - F: FnMut(Token<'s>) -> bool, - { - while self.peek().map_or(false, |t| f(t)) { - self.eat(); - } - } - - /// Consume the next token if the closure maps it a to `Some`-variant. - pub fn eat_map(&mut self, f: F) -> Option - where - F: FnOnce(Token<'s>) -> Option, - { - let token = self.peek()?; - let mapped = f(token); - if mapped.is_some() { - self.bump(); - } - mapped - } - - /// Eat the next token and return its source range. - pub fn eat_span(&mut self) -> Span { - let start = self.next_start(); - self.eat(); - Span::new(start, self.prev_end()) - } - - /// Consume the next token if it is the given one and produce an error if - /// not. - pub fn expect(&mut self, t: Token) -> bool { - let eaten = self.eat_if(t); - if !eaten { - self.expected_at(self.prev_end(), t.name()); - } - eaten - } - - /// Consume the next token, debug-asserting that it is one of the given ones. - pub fn assert(&mut self, t: Token) { - let next = self.eat(); - debug_assert_eq!(next, Some(t)); - } - - /// The index at which the last token ended. - /// - /// Refers to the end of the last _non-whitespace_ token in code mode. - pub fn prev_end(&self) -> usize { - self.prev_end - } - - /// The index at which the next token starts. - pub fn next_start(&self) -> usize { - self.next_start - } - - /// The index at which the next token will end. - /// - /// Is the same as [`next_start()`][Self::next_start] if `peek()` returns - /// `None`. - pub fn next_end(&self) -> usize { - self.tokens.index() - } - - /// Determine the column for the given index in the source. - pub fn column(&self, index: usize) -> usize { - self.source.pos_to_column(index.into()).unwrap() - } - - /// The span from `start` to [`self.prev_end()`](Self::prev_end). - pub fn span(&self, start: impl Into) -> Span { - Span::new(start, self.prev_end()) - } - - /// Return the source string from `start` to the end of the previous token. - pub fn eaten_from(&self, start: usize) -> &'s str { - self.tokens.scanner().get(start .. self.prev_end()) - } - - /// Jump to an index in the string. - /// - /// You need to know the correct column. - fn jump(&mut self, index: usize) { - self.tokens.jump(index); - self.bump(); - } - /// Move to the next token. fn bump(&mut self) { - self.prev_end = self.tokens.index(); - self.next_start = self.tokens.index(); + self.prev_end = self.tokens.index().into(); + self.next_start = self.tokens.index().into(); self.next = self.tokens.next(); if self.tokens.mode() == TokenMode::Code { @@ -355,7 +342,7 @@ impl<'s> Parser<'s> { Some(Token::BlockComment(_)) => true, _ => false, } { - self.next_start = self.tokens.index(); + self.next_start = self.tokens.index().into(); self.next = self.tokens.next(); } } @@ -399,7 +386,7 @@ impl<'s> Parser<'s> { impl Debug for Parser<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut s = self.tokens.scanner(); - s.jump(self.next_start()); + s.jump(self.next_start().to_usize()); write!(f, "Parser({}|{})", s.eaten(), s.rest()) } } diff --git a/src/source.rs b/src/source.rs index 20ba137f2..0a5a28609 100644 --- a/src/source.rs +++ b/src/source.rs @@ -63,7 +63,7 @@ impl SourceStore { io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") })?; - Ok(self.insert(Some(hash), path, src)) + Ok(self.insert(path, src, Some(hash))) } /// Directly provide a source file. @@ -82,16 +82,16 @@ impl SourceStore { id } else { // Not loaded yet. - self.insert(Some(hash), path, src) + self.insert(path, src, Some(hash)) } } else { // Not known to the loader. - self.insert(None, path, src) + self.insert(path, src, None) } } /// Insert a new source file. - fn insert(&mut self, hash: Option, path: &Path, src: String) -> SourceId { + fn insert(&mut self, path: &Path, src: String, hash: Option) -> SourceId { let id = SourceId(self.sources.len() as u32); if let Some(hash) = hash { self.files.insert(hash, id); @@ -112,6 +112,9 @@ impl SourceStore { } /// A single source file. +/// +/// _Note_: All line and column indices start at zero, just like byte indices. +/// Only for user-facing display, you should add 1 to them. pub struct SourceFile { id: SourceId, path: PathBuf, @@ -120,7 +123,8 @@ pub struct SourceFile { } impl SourceFile { - fn new(id: SourceId, path: &Path, src: String) -> Self { + /// Create a new source file. + pub fn new(id: SourceId, path: &Path, src: String) -> Self { let mut line_starts = vec![Pos::ZERO]; let mut s = Scanner::new(&src); @@ -151,7 +155,7 @@ impl SourceFile { self.id } - /// The path to the source file. + /// The normalized path to the source file. pub fn path(&self) -> &Path { &self.path } @@ -161,6 +165,11 @@ impl SourceFile { &self.src } + /// Slice out the part of the source code enclosed by the span. + pub fn get(&self, span: impl Into) -> Option<&str> { + self.src.get(span.into().to_range()) + } + /// Get the length of the file in bytes. pub fn len_bytes(&self) -> usize { self.src.len() @@ -171,11 +180,6 @@ impl SourceFile { self.line_starts.len() } - /// Slice out the part of the source code enclosed by the span. - pub fn get(&self, span: Span) -> Option<&str> { - self.src.get(span.to_range()) - } - /// Return the index of the line that contains the given byte position. pub fn pos_to_line(&self, byte_pos: Pos) -> Option { (byte_pos.to_usize() <= self.src.len()).then(|| { @@ -186,14 +190,15 @@ impl SourceFile { }) } - /// Return the column of the byte index. + /// Return the index of the column at the byte index. /// - /// Tabs are counted as occupying two columns. + /// The column is defined as the number of characters in the line before the + /// byte position. pub fn pos_to_column(&self, byte_pos: Pos) -> Option { let line = self.pos_to_line(byte_pos)?; let start = self.line_to_pos(line)?; let head = self.get(Span::new(start, byte_pos))?; - Some(head.chars().map(width).sum()) + Some(head.chars().count()) } /// Return the byte position at which the given line starts. @@ -210,32 +215,19 @@ impl SourceFile { /// Return the byte position of the given (line, column) pair. /// - /// Tabs are counted as occupying two columns. + /// The column defines the number of characters to go beyond the start of + /// the line. pub fn line_column_to_pos(&self, line_idx: usize, column_idx: usize) -> Option { let span = self.line_to_span(line_idx)?; let line = self.get(span)?; - - if column_idx == 0 { - return Some(span.start); + let mut chars = line.chars(); + for _ in 0 .. column_idx { + chars.next(); } - - let mut column = 0; - for (i, c) in line.char_indices() { - column += width(c); - if column >= column_idx { - return Some(span.start + Pos::from(i + c.len_utf8())); - } - } - - None + Some(span.start + (line.len() - chars.as_str().len())) } } -/// The display width of the character. -fn width(c: char) -> usize { - if c == '\t' { 2 } else { 1 } -} - impl AsRef for SourceFile { fn as_ref(&self) -> &str { &self.src @@ -256,14 +248,34 @@ impl<'a> Files<'a> for SourceStore { Ok(self.get(id)) } - fn line_index( + fn line_index(&'a self, id: SourceId, given: usize) -> Result { + let source = self.get(id); + source + .pos_to_line(given.into()) + .ok_or_else(|| files::Error::IndexTooLarge { given, max: source.len_bytes() }) + } + + fn line_range( &'a self, id: SourceId, - byte_index: usize, + given: usize, + ) -> Result, files::Error> { + let source = self.get(id); + source + .line_to_span(given) + .map(Span::to_range) + .ok_or_else(|| files::Error::LineTooLarge { given, max: source.len_lines() }) + } + + fn column_number( + &'a self, + id: SourceId, + _: usize, + given: usize, ) -> Result { let source = self.get(id); - source.pos_to_line(byte_index.into()).ok_or_else(|| { - let (given, max) = (byte_index, source.len_bytes()); + source.pos_to_column(given.into()).ok_or_else(|| { + let max = source.len_bytes(); if given <= max { files::Error::InvalidCharBoundary { given } } else { @@ -271,28 +283,13 @@ impl<'a> Files<'a> for SourceStore { } }) } - - fn line_range( - &'a self, - id: SourceId, - line_index: usize, - ) -> Result, files::Error> { - let source = self.get(id); - match source.line_to_span(line_index) { - Some(span) => Ok(span.to_range()), - None => Err(files::Error::LineTooLarge { - given: line_index, - max: source.len_lines(), - }), - } - } } #[cfg(test)] mod tests { use super::*; - const TEST: &str = "äbcde\nf💛g\r\nhi\rjkl"; + const TEST: &str = "ä\tcde\nf💛g\r\nhi\rjkl"; #[test] fn test_source_file_new() { @@ -313,6 +310,17 @@ mod tests { assert_eq!(source.pos_to_line(Pos(22)), None); } + #[test] + fn test_source_file_pos_to_column() { + let source = SourceFile::detached(TEST); + assert_eq!(source.pos_to_column(Pos(0)), Some(0)); + assert_eq!(source.pos_to_column(Pos(2)), Some(1)); + assert_eq!(source.pos_to_column(Pos(6)), Some(5)); + assert_eq!(source.pos_to_column(Pos(7)), Some(0)); + assert_eq!(source.pos_to_column(Pos(8)), Some(1)); + assert_eq!(source.pos_to_column(Pos(12)), Some(2)); + } + #[test] fn test_source_file_roundtrip() { #[track_caller] diff --git a/tests/ref/markup/lists.png b/tests/ref/markup/lists.png index 815e51056515c57169d79c987babe107e97e5605..315905b8ef38656775711c11849becc01d01a51a 100644 GIT binary patch literal 15207 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 w3Ao(38%n~St