mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Incremental-safety based approach
This commit is contained in:
parent
7016ab0d12
commit
eba7fc34ef
@ -25,18 +25,36 @@ pub fn parse(src: &str) -> Rc<GreenNode> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a block. Returns `Some` if there was only one block.
|
||||
pub fn parse_block(source: &str) -> Option<Rc<GreenNode>> {
|
||||
/// Parse an atomic primary. Returns `Some` if all of the input was consumed.
|
||||
pub fn parse_atomic(source: &str, _: bool) -> Option<Vec<Green>> {
|
||||
let mut p = Parser::new(source);
|
||||
block(&mut p);
|
||||
if p.eof() {
|
||||
match p.finish().into_iter().next() {
|
||||
Some(Green::Node(node)) => Some(node),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
primary(&mut p, true).ok()?;
|
||||
p.eject()
|
||||
}
|
||||
|
||||
/// Parse some markup. Returns `Some` if all of the input was consumed.
|
||||
pub fn parse_markup(source: &str, _: bool) -> Option<Vec<Green>> {
|
||||
let mut p = Parser::new(source);
|
||||
markup(&mut p);
|
||||
p.eject()
|
||||
}
|
||||
|
||||
/// Parse some markup without the topmost node. Returns `Some` if all of the
|
||||
/// input was consumed.
|
||||
pub fn parse_markup_elements(source: &str, mut at_start: bool) -> Option<Vec<Green>> {
|
||||
let mut p = Parser::new(source);
|
||||
while !p.eof() {
|
||||
markup_node(&mut p, &mut at_start);
|
||||
}
|
||||
p.eject()
|
||||
}
|
||||
|
||||
/// Parse some code. Returns `Some` if all of the input was consumed.
|
||||
pub fn parse_code(source: &str, _: bool) -> Option<Vec<Green>> {
|
||||
let mut p = Parser::new(source);
|
||||
p.set_mode(TokenMode::Code);
|
||||
expr_list(&mut p);
|
||||
p.eject()
|
||||
}
|
||||
|
||||
/// Parse markup.
|
||||
@ -118,7 +136,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
|
||||
|
||||
// Line-based markup that is not currently at the start of the line.
|
||||
NodeKind::Eq | NodeKind::Minus | NodeKind::EnumNumbering(_) => {
|
||||
p.convert(NodeKind::Text(p.peek_src().into()));
|
||||
p.convert(NodeKind::TextInLine(p.peek_src().into()))
|
||||
}
|
||||
|
||||
// Hashtag + keyword / identifier.
|
||||
@ -196,7 +214,7 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
|
||||
let marker = p.marker();
|
||||
|
||||
// Start the unary expression.
|
||||
match p.peek().and_then(UnOp::from_token) {
|
||||
match (!atomic).then(|| p.peek().and_then(UnOp::from_token)).flatten() {
|
||||
Some(op) => {
|
||||
p.eat();
|
||||
let prec = op.precedence();
|
||||
@ -268,7 +286,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
|
||||
}
|
||||
|
||||
// Structures.
|
||||
Some(NodeKind::LeftParen) => parenthesized(p),
|
||||
Some(NodeKind::LeftParen) => parenthesized(p, atomic),
|
||||
Some(NodeKind::LeftBracket) => {
|
||||
template(p);
|
||||
Ok(())
|
||||
@ -329,7 +347,7 @@ fn literal(p: &mut Parser) -> bool {
|
||||
/// - Dictionary literal
|
||||
/// - Parenthesized expression
|
||||
/// - Parameter list of closure expression
|
||||
fn parenthesized(p: &mut Parser) -> ParseResult {
|
||||
fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult {
|
||||
let marker = p.marker();
|
||||
|
||||
p.start_group(Group::Paren);
|
||||
@ -344,7 +362,7 @@ fn parenthesized(p: &mut Parser) -> ParseResult {
|
||||
}
|
||||
|
||||
// Arrow means this is a closure's parameter list.
|
||||
if p.at(&NodeKind::Arrow) {
|
||||
if !atomic && p.at(&NodeKind::Arrow) {
|
||||
params(p, marker);
|
||||
p.eat_assert(&NodeKind::Arrow);
|
||||
return marker.perform(p, NodeKind::Closure, expr);
|
||||
@ -507,20 +525,25 @@ fn template(p: &mut Parser) {
|
||||
fn block(p: &mut Parser) {
|
||||
p.perform(NodeKind::Block, |p| {
|
||||
p.start_group(Group::Brace);
|
||||
while !p.eof() {
|
||||
p.start_group(Group::Stmt);
|
||||
if expr(p).is_ok() && !p.eof() {
|
||||
p.expected_at("semicolon or line break");
|
||||
}
|
||||
p.end_group();
|
||||
|
||||
// Forcefully skip over newlines since the group's contents can't.
|
||||
p.eat_while(|t| matches!(t, NodeKind::Space(_)));
|
||||
}
|
||||
expr_list(p);
|
||||
p.end_group();
|
||||
});
|
||||
}
|
||||
|
||||
/// Parse a number of code expressions.
|
||||
fn expr_list(p: &mut Parser) {
|
||||
while !p.eof() {
|
||||
p.start_group(Group::Stmt);
|
||||
if expr(p).is_ok() && !p.eof() {
|
||||
p.expected_at("semicolon or line break");
|
||||
}
|
||||
p.end_group();
|
||||
|
||||
// Forcefully skip over newlines since the group's contents can't.
|
||||
p.eat_while(|t| matches!(t, NodeKind::Space(_)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a function call.
|
||||
fn call(p: &mut Parser, callee: Marker) -> ParseResult {
|
||||
callee.perform(p, NodeKind::Call, |p| args(p, true, true))
|
||||
|
@ -21,6 +21,8 @@ pub struct Parser<'s> {
|
||||
groups: Vec<GroupEntry>,
|
||||
/// The children of the currently built node.
|
||||
children: Vec<Green>,
|
||||
/// Whether the last group was terminated.
|
||||
last_group_terminated: bool,
|
||||
}
|
||||
|
||||
impl<'s> Parser<'s> {
|
||||
@ -36,6 +38,7 @@ impl<'s> Parser<'s> {
|
||||
current_start: 0,
|
||||
groups: vec![],
|
||||
children: vec![],
|
||||
last_group_terminated: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +47,15 @@ impl<'s> Parser<'s> {
|
||||
self.children
|
||||
}
|
||||
|
||||
/// End the parsing process and return multiple children.
|
||||
pub fn eject(self) -> Option<Vec<Green>> {
|
||||
if self.eof() && self.group_success() {
|
||||
Some(self.children)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new marker.
|
||||
pub fn marker(&mut self) -> Marker {
|
||||
Marker(self.children.len())
|
||||
@ -190,6 +202,11 @@ impl<'s> Parser<'s> {
|
||||
self.tokens.scanner().column(index)
|
||||
}
|
||||
|
||||
/// Set the tokenizer's mode.
|
||||
pub fn set_mode(&mut self, mode: TokenMode) {
|
||||
self.tokens.set_mode(mode);
|
||||
}
|
||||
|
||||
/// Continue parsing in a group.
|
||||
///
|
||||
/// When the end delimiter of the group is reached, all subsequent calls to
|
||||
@ -225,6 +242,7 @@ impl<'s> Parser<'s> {
|
||||
let group = self.groups.pop().expect("no started group");
|
||||
self.tokens.set_mode(group.prev_mode);
|
||||
self.repeek();
|
||||
self.last_group_terminated = true;
|
||||
|
||||
let mut rescan = self.tokens.mode() != group_mode;
|
||||
|
||||
@ -243,6 +261,7 @@ impl<'s> Parser<'s> {
|
||||
rescan = false;
|
||||
} else if required {
|
||||
self.push_error(format_eco!("expected {}", end));
|
||||
self.last_group_terminated = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,6 +279,11 @@ impl<'s> Parser<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the group processing was successfully terminated.
|
||||
pub fn group_success(&self) -> bool {
|
||||
self.last_group_terminated && self.groups.is_empty()
|
||||
}
|
||||
|
||||
/// Low-level bump that consumes exactly one token without special trivia
|
||||
/// handling.
|
||||
fn bump(&mut self) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::ops::{Not, Range};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::diag::TypResult;
|
||||
use crate::loading::{FileHash, Loader};
|
||||
use crate::parse::{is_newline, parse, parse_block, Scanner};
|
||||
use crate::parse::{is_newline, parse, Scanner};
|
||||
use crate::syntax::ast::Markup;
|
||||
use crate::syntax::{self, Category, GreenNode, NodeKind, RedNode, Span};
|
||||
use crate::syntax::{self, Category, GreenNode, RedNode, Span};
|
||||
use crate::util::PathExt;
|
||||
|
||||
#[cfg(feature = "codespan-reporting")]
|
||||
@ -285,27 +285,10 @@ impl SourceFile {
|
||||
|
||||
// Update the root node.
|
||||
let insertion_span = Span::new(self.id, replace.start, replace.end);
|
||||
let incremental_target =
|
||||
Rc::make_mut(&mut self.root).incremental_parent(insertion_span);
|
||||
|
||||
match incremental_target {
|
||||
Some((child, offset)) => {
|
||||
let src = &self.src[offset .. offset + child.len()];
|
||||
let parse_res = match child.kind() {
|
||||
NodeKind::Markup => Some(parse(src)),
|
||||
_ => parse_block(src),
|
||||
}
|
||||
.and_then(|x| x.erroneous.not().then(|| x));
|
||||
|
||||
if let Some(parse_res) = parse_res {
|
||||
*child = Rc::try_unwrap(parse_res).unwrap();
|
||||
} else {
|
||||
self.root = parse(&self.src);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.root = parse(&self.src);
|
||||
}
|
||||
let source = self.src().to_string();
|
||||
if !Rc::make_mut(&mut self.root).incremental(&source, insertion_span, with.len())
|
||||
{
|
||||
self.root = parse(self.src());
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,6 +494,6 @@ mod tests {
|
||||
}
|
||||
|
||||
// Test inserting at the begining.
|
||||
test("abc #f()[def] ghi", 10 .. 11, "xyz", "abc #f()[dxyzf] ghi");
|
||||
test("abc #f()[def] ghi", 5 .. 6, "g", "abc #g()[def] ghi");
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,9 @@ impl Markup {
|
||||
NodeKind::Parbreak => Some(MarkupNode::Parbreak),
|
||||
NodeKind::Strong => Some(MarkupNode::Strong),
|
||||
NodeKind::Emph => Some(MarkupNode::Emph),
|
||||
NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())),
|
||||
NodeKind::Text(s) | NodeKind::TextInLine(s) => {
|
||||
Some(MarkupNode::Text(s.clone()))
|
||||
}
|
||||
NodeKind::Escape(c) => Some(MarkupNode::Text((*c).into())),
|
||||
NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())),
|
||||
NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())),
|
||||
|
@ -158,6 +158,7 @@ impl Category {
|
||||
NodeKind::Space(_) => None,
|
||||
NodeKind::Parbreak => None,
|
||||
NodeKind::Text(_) => None,
|
||||
NodeKind::TextInLine(_) => None,
|
||||
NodeKind::List => None,
|
||||
NodeKind::Enum => None,
|
||||
NodeKind::Array => None,
|
||||
|
@ -15,6 +15,9 @@ pub use span::*;
|
||||
use self::ast::{MathNode, RawNode, TypedNode};
|
||||
use crate::diag::Error;
|
||||
use crate::geom::{AngularUnit, LengthUnit};
|
||||
use crate::parse::{
|
||||
parse_atomic, parse_code, parse_markup, parse_markup_elements, TokenMode,
|
||||
};
|
||||
use crate::source::SourceId;
|
||||
use crate::util::EcoString;
|
||||
|
||||
@ -73,6 +76,43 @@ impl Green {
|
||||
Self::Token(data) => data.kind = kind,
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the innermost child that is incremental safe.
|
||||
pub fn incremental_int(
|
||||
&mut self,
|
||||
edit: &str,
|
||||
replace: Span,
|
||||
replacement_len: usize,
|
||||
offset: usize,
|
||||
parent_mode: TokenMode,
|
||||
outermost: bool,
|
||||
) -> bool {
|
||||
match self {
|
||||
Green::Node(n) => Rc::make_mut(n).incremental_int(
|
||||
edit,
|
||||
replace,
|
||||
replacement_len,
|
||||
offset,
|
||||
parent_mode,
|
||||
outermost,
|
||||
),
|
||||
Green::Token(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The error messages for this node and its descendants.
|
||||
pub fn errors(&self) -> Vec<NodeKind> {
|
||||
match self {
|
||||
Green::Node(n) => n.errors(),
|
||||
Green::Token(t) => {
|
||||
if t.kind().is_error() {
|
||||
vec![t.kind().clone()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Green {
|
||||
@ -148,60 +188,181 @@ impl GreenNode {
|
||||
self.data().len()
|
||||
}
|
||||
|
||||
/// Find the deepest incremental-safe node and its offset in the source
|
||||
/// code.
|
||||
pub fn incremental_parent(&mut self, span: Span) -> Option<(&mut GreenNode, usize)> {
|
||||
self.incremental_parent_internal(span, 0)
|
||||
/// The error messages for this node and its descendants.
|
||||
pub fn errors(&self) -> Vec<NodeKind> {
|
||||
let mut res = self.children.iter().flat_map(|c| c.errors()).collect::<Vec<_>>();
|
||||
|
||||
if self.kind().is_error() {
|
||||
res.push(self.kind().clone());
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn incremental_parent_internal(
|
||||
/// Find the innermost child that is incremental safe.
|
||||
pub fn incremental(
|
||||
&mut self,
|
||||
span: Span,
|
||||
edit: &str,
|
||||
replace: Span,
|
||||
replacement_len: usize,
|
||||
) -> bool {
|
||||
self.incremental_int(edit, replace, replacement_len, 0, TokenMode::Markup, true)
|
||||
}
|
||||
|
||||
fn incremental_int(
|
||||
&mut self,
|
||||
src: &str,
|
||||
replace: Span,
|
||||
replacement_len: usize,
|
||||
mut offset: usize,
|
||||
) -> Option<(&mut GreenNode, usize)> {
|
||||
for child in self.children.iter_mut() {
|
||||
match child {
|
||||
Green::Token(n) => {
|
||||
if offset < span.start {
|
||||
// the token is strictly before the span
|
||||
offset += n.len();
|
||||
} else {
|
||||
// the token is within or after the span; tokens are
|
||||
// never safe, so we return.
|
||||
return None;
|
||||
}
|
||||
parent_mode: TokenMode,
|
||||
outermost: bool,
|
||||
) -> bool {
|
||||
let kind = self.kind().clone();
|
||||
let mode = kind.mode().apply(parent_mode);
|
||||
eprintln!("in {:?} (mode {:?})", kind, mode);
|
||||
|
||||
let mut loop_result = None;
|
||||
let mut child_at_start = true;
|
||||
let last = self.children.len() - 1;
|
||||
for (i, child) in self.children.iter_mut().enumerate() {
|
||||
let child_span = Span::new(replace.source, offset, offset + child.len());
|
||||
if child_span.surrounds(replace) {
|
||||
eprintln!("found correct child");
|
||||
|
||||
// First, we try if the child has another, more specific applicable child.
|
||||
if kind.incremental_safety() != IncrementalSafety::Unsafe
|
||||
&& child.incremental_int(
|
||||
src,
|
||||
replace,
|
||||
replacement_len,
|
||||
offset,
|
||||
mode,
|
||||
i == last && outermost,
|
||||
)
|
||||
{
|
||||
eprintln!("child was successful");
|
||||
return true;
|
||||
}
|
||||
Green::Node(n) => {
|
||||
let end = n.len() + offset;
|
||||
if offset < span.start && end < span.start {
|
||||
// the node is strictly before the span
|
||||
offset += n.len();
|
||||
} else if span.start >= offset
|
||||
&& span.start < end
|
||||
&& span.end <= end
|
||||
&& span.end > offset
|
||||
{
|
||||
// the node is within the span.
|
||||
let safe = n.kind().is_incremental_safe();
|
||||
let mut_n = Rc::make_mut(n);
|
||||
if safe {
|
||||
let res = mut_n.incremental_parent_internal(span, offset);
|
||||
if res.is_none() {
|
||||
return Some((mut_n, offset));
|
||||
}
|
||||
} else {
|
||||
return mut_n.incremental_parent_internal(span, offset);
|
||||
}
|
||||
|
||||
// This didn't work, so we try to replace the child at this
|
||||
// level.
|
||||
let (function, policy) =
|
||||
if let Some(p) = child.kind().reparsing_function(mode) {
|
||||
p
|
||||
} else {
|
||||
// the node is overlapping or after after the span; nodes are
|
||||
// never safe, so we return.
|
||||
return None;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
loop_result = Some((i, child_span, function, policy));
|
||||
break;
|
||||
}
|
||||
|
||||
offset += child.len();
|
||||
child_at_start = child.kind().is_at_start(child_at_start);
|
||||
}
|
||||
|
||||
|
||||
// We now have a child that we can replace and a function to do so if
|
||||
// the loop found any results at all.
|
||||
let (child_idx, child_span, func, policy) = if let Some(loop_result) = loop_result
|
||||
{
|
||||
loop_result
|
||||
} else {
|
||||
// No child fully contains the replacement.
|
||||
eprintln!("no child match");
|
||||
return false;
|
||||
};
|
||||
|
||||
eprintln!("aquired function, policy {:?}", policy);
|
||||
|
||||
let src_span = child_span.inserted(replace, replacement_len);
|
||||
|
||||
let new_children =
|
||||
if let Some(new_children) = func(&src[src_span.to_range()], child_at_start) {
|
||||
new_children
|
||||
} else {
|
||||
eprintln!("function failed");
|
||||
return false;
|
||||
};
|
||||
let child_mode = self.children[child_idx].kind().mode().apply(mode);
|
||||
eprintln!("child mode {:?}", child_mode);
|
||||
|
||||
// Check if the children / child has the right type.
|
||||
let require_single = match policy {
|
||||
IncrementalSafety::AtomicPrimary | IncrementalSafety::SameKind => true,
|
||||
IncrementalSafety::SameKindInCode if child_mode == TokenMode::Code => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if require_single {
|
||||
eprintln!("must be a single replacement");
|
||||
if new_children.len() != 1 {
|
||||
eprintln!("not a single replacement");
|
||||
return false;
|
||||
}
|
||||
|
||||
if match policy {
|
||||
IncrementalSafety::SameKind => true,
|
||||
IncrementalSafety::SameKindInCode if child_mode == TokenMode::Code => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
} {
|
||||
if self.children[child_idx].kind() != new_children[0].kind() {
|
||||
eprintln!("not the same kind");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
// Do not accept unclosed nodes if the old node did not use to be at the
|
||||
// right edge of the tree.
|
||||
if !outermost
|
||||
&& new_children
|
||||
.iter()
|
||||
.flat_map(|x| x.errors())
|
||||
.any(|x| matches!(x, NodeKind::Error(ErrorPos::End, _)))
|
||||
{
|
||||
eprintln!("unclosed node");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the neighbor invariants are still true.
|
||||
if mode == TokenMode::Markup {
|
||||
if child_idx > 0 {
|
||||
if self.children[child_idx - 1].kind().incremental_safety()
|
||||
== IncrementalSafety::EnsureRightWhitespace
|
||||
&& !new_children[0].kind().is_whitespace()
|
||||
{
|
||||
eprintln!("left whitespace missing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_at_start = child_at_start;
|
||||
for child in &new_children {
|
||||
new_at_start = child.kind().is_at_start(new_at_start);
|
||||
}
|
||||
|
||||
for child in &self.children[child_idx + 1 ..] {
|
||||
if child.kind().is_trivia() {
|
||||
new_at_start = child.kind().is_at_start(new_at_start);
|
||||
continue;
|
||||
}
|
||||
|
||||
match child.kind().incremental_safety() {
|
||||
IncrementalSafety::EnsureAtStart if !new_at_start => return false,
|
||||
IncrementalSafety::EnsureNotAtStart if new_at_start => return false,
|
||||
_ => {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("... replacing");
|
||||
|
||||
self.children.splice(child_idx .. child_idx + 1, new_children);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,6 +558,7 @@ impl<'a> RedRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The error messages for this node and its descendants.
|
||||
pub fn errors(self) -> Vec<Error> {
|
||||
if !self.green.erroneous() {
|
||||
@ -593,6 +755,8 @@ pub enum NodeKind {
|
||||
Parbreak,
|
||||
/// A consecutive non-markup string.
|
||||
Text(EcoString),
|
||||
/// A text node that cannot appear at the beginning of a source line.
|
||||
TextInLine(EcoString),
|
||||
/// A non-breaking space: `~`.
|
||||
NonBreakingSpace,
|
||||
/// An en-dash: `--`.
|
||||
@ -729,19 +893,249 @@ impl NodeKind {
|
||||
matches!(self, Self::LeftParen | Self::RightParen)
|
||||
}
|
||||
|
||||
/// Whether this is whitespace.
|
||||
pub fn is_whitespace(&self) -> bool {
|
||||
match self {
|
||||
Self::Space(_) | Self::Parbreak => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is trivia.
|
||||
pub fn is_trivia(&self) -> bool {
|
||||
match self {
|
||||
_ if self.is_whitespace() => true,
|
||||
Self::LineComment | Self::BlockComment => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is some kind of error.
|
||||
pub fn is_error(&self) -> bool {
|
||||
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
|
||||
}
|
||||
|
||||
/// Whether it is safe to do incremental parsing on this node.
|
||||
pub fn is_incremental_safe(&self) -> bool {
|
||||
/// Whether this node is `at_start` given the previous value of the property.
|
||||
pub fn is_at_start(&self, prev: bool) -> bool {
|
||||
match self {
|
||||
Self::Block | Self::Markup => true,
|
||||
Self::Space(n) if *n > 0 => true,
|
||||
Self::Parbreak => true,
|
||||
Self::LineComment | Self::BlockComment => prev,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this token appears in Markup.
|
||||
pub fn mode(&self) -> NodeMode {
|
||||
match self {
|
||||
Self::Markup
|
||||
| Self::Space(_)
|
||||
| Self::Parbreak
|
||||
| Self::Text(_)
|
||||
| Self::TextInLine(_)
|
||||
| Self::NonBreakingSpace
|
||||
| Self::EnDash
|
||||
| Self::EmDash
|
||||
| Self::Escape(_)
|
||||
| Self::Strong
|
||||
| Self::Emph
|
||||
| Self::Math(_) => NodeMode::Markup,
|
||||
Self::Template
|
||||
| Self::Block
|
||||
| Self::None
|
||||
| Self::Auto
|
||||
| Self::Ident(_)
|
||||
| Self::Bool(_)
|
||||
| Self::Int(_)
|
||||
| Self::Float(_)
|
||||
| Self::Length(_, _)
|
||||
| Self::Angle(_, _)
|
||||
| Self::Percentage(_)
|
||||
| Self::Str(_)
|
||||
| Self::Fraction(_)
|
||||
| Self::Array
|
||||
| Self::Dict
|
||||
| Self::Group
|
||||
| Self::Call
|
||||
| Self::LineComment
|
||||
| Self::BlockComment
|
||||
| Self::Error(_, _)
|
||||
| Self::Minus
|
||||
| Self::Eq => NodeMode::Universal,
|
||||
_ => NodeMode::Code,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reparsing_function(
|
||||
&self,
|
||||
parent_mode: TokenMode,
|
||||
) -> Option<(fn(&str, bool) -> Option<Vec<Green>>, IncrementalSafety)> {
|
||||
let policy = self.incremental_safety();
|
||||
if policy == IncrementalSafety::Unsafe {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mode = self.mode();
|
||||
if mode == NodeMode::Code && policy == IncrementalSafety::UnsafeLayer {
|
||||
return None;
|
||||
}
|
||||
|
||||
if mode != NodeMode::Markup
|
||||
&& parent_mode == TokenMode::Code
|
||||
&& policy == IncrementalSafety::AtomicPrimary
|
||||
{
|
||||
return Some((parse_atomic, policy));
|
||||
}
|
||||
|
||||
let parser: fn(&str, bool) -> _ = match mode {
|
||||
NodeMode::Code => parse_code,
|
||||
NodeMode::Markup if self == &Self::Markup => parse_markup,
|
||||
NodeMode::Markup => parse_markup_elements,
|
||||
NodeMode::Universal if parent_mode == TokenMode::Code => parse_code,
|
||||
NodeMode::Universal => parse_markup_elements,
|
||||
};
|
||||
|
||||
Some((parser, policy))
|
||||
}
|
||||
|
||||
/// Whether it is safe to do incremental parsing on this node. Never allow
|
||||
/// non-termination errors if this is not already the last leaf node.
|
||||
pub fn incremental_safety(&self) -> IncrementalSafety {
|
||||
match self {
|
||||
// Replacing parenthesis changes if the expression is balanced and
|
||||
// is therefore not safe.
|
||||
Self::LeftBracket
|
||||
| Self::RightBracket
|
||||
| Self::LeftBrace
|
||||
| Self::RightBrace
|
||||
| Self::LeftParen
|
||||
| Self::RightParen => IncrementalSafety::Unsafe,
|
||||
|
||||
// Replacing an operator can change whether the parent is an
|
||||
// operation which makes it unsafe. The star can appear in markup.
|
||||
Self::Star
|
||||
| Self::Comma
|
||||
| Self::Semicolon
|
||||
| Self::Colon
|
||||
| Self::Plus
|
||||
| Self::Minus
|
||||
| Self::Slash
|
||||
| Self::Eq
|
||||
| Self::EqEq
|
||||
| Self::ExclEq
|
||||
| Self::Lt
|
||||
| Self::LtEq
|
||||
| Self::Gt
|
||||
| Self::GtEq
|
||||
| Self::PlusEq
|
||||
| Self::HyphEq
|
||||
| Self::StarEq
|
||||
| Self::SlashEq
|
||||
| Self::Not
|
||||
| Self::And
|
||||
| Self::Or
|
||||
| Self::With
|
||||
| Self::Dots
|
||||
| Self::Arrow => IncrementalSafety::Unsafe,
|
||||
|
||||
// These keywords are literals and can be safely be substituted with
|
||||
// other expressions.
|
||||
Self::None | Self::Auto => IncrementalSafety::AtomicPrimary,
|
||||
|
||||
// These keywords change what kind of expression the parent is.
|
||||
Self::Let
|
||||
| Self::If
|
||||
| Self::Else
|
||||
| Self::For
|
||||
| Self::In
|
||||
| Self::While
|
||||
| Self::Break
|
||||
| Self::Continue
|
||||
| Self::Return
|
||||
| Self::Set
|
||||
| Self::Import
|
||||
| Self::Include
|
||||
| Self::From => IncrementalSafety::Unsafe,
|
||||
|
||||
// This is a backslash followed by a space. But changing it to
|
||||
// anything else is fair game.
|
||||
Self::Linebreak => IncrementalSafety::EnsureRightWhitespace,
|
||||
|
||||
Self::Markup => IncrementalSafety::SameKind,
|
||||
|
||||
Self::Space(_) => IncrementalSafety::SameKindInCode,
|
||||
|
||||
// These are all replaceable by other tokens.
|
||||
Self::Parbreak
|
||||
| Self::Text(_)
|
||||
| Self::NonBreakingSpace
|
||||
| Self::EnDash
|
||||
| Self::EmDash
|
||||
| Self::Escape(_)
|
||||
| Self::Strong
|
||||
| Self::Emph => IncrementalSafety::Safe,
|
||||
|
||||
// This is text that needs to be not `at_start`, otherwise it would
|
||||
// start one of the below items.
|
||||
Self::TextInLine(_) => IncrementalSafety::EnsureNotAtStart,
|
||||
|
||||
// These have to be `at_start` so they must be preceeded with a
|
||||
// Space(n) with n > 0 or a Parbreak.
|
||||
Self::Heading | Self::Enum | Self::List => IncrementalSafety::EnsureAtStart,
|
||||
|
||||
// Changing the heading level, enum numbering, or list bullet
|
||||
// changes the next layer.
|
||||
Self::EnumNumbering(_) => IncrementalSafety::Unsafe,
|
||||
|
||||
Self::Raw(_) | Self::Math(_) => IncrementalSafety::Safe,
|
||||
|
||||
// These are expressions that can be replaced by other expressions.
|
||||
Self::Ident(_)
|
||||
| Self::Bool(_)
|
||||
| Self::Int(_)
|
||||
| Self::Float(_)
|
||||
| Self::Length(_, _)
|
||||
| Self::Angle(_, _)
|
||||
| Self::Percentage(_)
|
||||
| Self::Str(_)
|
||||
| Self::Fraction(_)
|
||||
| Self::Array
|
||||
| Self::Dict
|
||||
| Self::Group => IncrementalSafety::AtomicPrimary,
|
||||
|
||||
Self::Call | Self::Unary | Self::Binary | Self::SetExpr => {
|
||||
IncrementalSafety::UnsafeLayer
|
||||
}
|
||||
|
||||
Self::CallArgs | Self::Named | Self::Spread => IncrementalSafety::UnsafeLayer,
|
||||
|
||||
// The closure is a bit magic with the let expression, and also it
|
||||
// is not atomic.
|
||||
Self::Closure | Self::ClosureParams => IncrementalSafety::UnsafeLayer,
|
||||
|
||||
// These can appear as bodies and would trigger an error if they
|
||||
// became something else.
|
||||
Self::Template | Self::Block => IncrementalSafety::SameKindInCode,
|
||||
|
||||
Self::ForExpr
|
||||
| Self::WhileExpr
|
||||
| Self::IfExpr
|
||||
| Self::LetExpr
|
||||
| Self::ImportExpr
|
||||
| Self::IncludeExpr => IncrementalSafety::AtomicPrimary,
|
||||
|
||||
Self::WithExpr | Self::ForPattern | Self::ImportItems => {
|
||||
IncrementalSafety::UnsafeLayer
|
||||
}
|
||||
|
||||
// These can appear everywhere and must not change to other stuff
|
||||
// because that could change the outer expression.
|
||||
Self::LineComment | Self::BlockComment => IncrementalSafety::SameKind,
|
||||
|
||||
Self::Error(_, _) | Self::Unknown(_) => IncrementalSafety::Unsafe,
|
||||
}
|
||||
}
|
||||
|
||||
/// A human-readable name for the kind.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
@ -794,7 +1188,7 @@ impl NodeKind {
|
||||
Self::Space(_) => "space",
|
||||
Self::Linebreak => "forced linebreak",
|
||||
Self::Parbreak => "paragraph break",
|
||||
Self::Text(_) => "text",
|
||||
Self::Text(_) | Self::TextInLine(_) => "text",
|
||||
Self::NonBreakingSpace => "non-breaking space",
|
||||
Self::EnDash => "en dash",
|
||||
Self::EmDash => "em dash",
|
||||
@ -855,3 +1249,87 @@ impl Display for NodeKind {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum describes what conditions a node has for being replaced by a new
|
||||
/// parse result.
|
||||
///
|
||||
/// Safe nodes are replaced by the new parse result from the respective mode.
|
||||
/// They can be replaced by multiple tokens. If a token is inserted in Markup
|
||||
/// mode and the next token would not be `at_start` there needs to be a forward
|
||||
/// check for a `EnsureAtStart` node. If this fails, the parent has to be
|
||||
/// reparsed. if the direct whitespace sibling of a `EnsureRightWhitespace` is
|
||||
/// `Unsafe`. Similarly, if a `EnsureRightWhitespace` token is one of the last
|
||||
/// tokens to be inserted, the edit is invalidated if there is no following
|
||||
/// whitespace. The atomic nodes may only be replaced by other atomic nodes. The
|
||||
/// unsafe layers cannot be used but allow children access, the unsafe nodes do
|
||||
/// neither.
|
||||
///
|
||||
/// *Procedure:*
|
||||
/// 1. Check if the node is safe - if unsafe layer recurse, if unsafe, return
|
||||
/// None.
|
||||
/// 2. Reparse with appropriate node kind and `at_start`.
|
||||
/// 3. Check whether the topmost group is terminated and the range was
|
||||
/// completely consumed, otherwise return None.
|
||||
/// 4. Check if the type criteria are met.
|
||||
/// 5. If the node is not at the end of the tree, check if Strings etc. are
|
||||
/// terminated.
|
||||
/// 6. If this is markup, check the following things:
|
||||
/// - The `at_start` conditions of the next non-comment and non-space(0) node
|
||||
/// are met.
|
||||
/// - The first node is whitespace or the previous siblings are not
|
||||
/// `EnsureRightWhitespace`.
|
||||
/// - If any of those fails, return None.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum IncrementalSafety {
|
||||
/// Changing this node can never have an influence on the other nodes.
|
||||
Safe,
|
||||
/// This node has to be replaced with a single token of the same kind.
|
||||
SameKind,
|
||||
/// This node has to be replaced with a single token of the same kind if in
|
||||
/// code mode.
|
||||
SameKindInCode,
|
||||
/// These nodes depend on being at the start of a line. Reparsing of safe
|
||||
/// left neighbors has to check this invariant. Otherwise, this node is
|
||||
/// safe.
|
||||
EnsureAtStart,
|
||||
/// These nodes depend on not being at the start of a line. Reparsing of
|
||||
/// safe left neighbors has to check this invariant. Otherwise, this node is
|
||||
/// safe.
|
||||
EnsureNotAtStart,
|
||||
/// These nodes must be followed by whitespace.
|
||||
EnsureRightWhitespace,
|
||||
/// Changing this node into a single atomic expression is allowed if it
|
||||
/// appears in code mode, otherwise it is safe.
|
||||
AtomicPrimary,
|
||||
/// Changing an unsafe layer node changes what the parents or the
|
||||
/// surrounding nodes would be and is therefore disallowed. Change the
|
||||
/// parents or children instead. If it appears in Markup, however, it is
|
||||
/// safe to change.
|
||||
UnsafeLayer,
|
||||
/// Changing an unsafe node or any of its children will trigger undefined
|
||||
/// behavior. Change the parents instead.
|
||||
Unsafe,
|
||||
}
|
||||
|
||||
/// This enum describes which mode a token of [`NodeKind`] can appear in.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum NodeMode {
|
||||
/// The token can only appear in markup mode.
|
||||
Markup,
|
||||
/// The token can only appear in code mode.
|
||||
Code,
|
||||
/// The token can appear in either mode. Look at the parent node to decide
|
||||
/// which mode it is in.
|
||||
Universal,
|
||||
}
|
||||
|
||||
impl NodeMode {
|
||||
/// Returns the new [`TokenMode`] given the old one.
|
||||
pub fn apply(&self, old: TokenMode) -> TokenMode {
|
||||
match self {
|
||||
Self::Markup => TokenMode::Markup,
|
||||
Self::Code => TokenMode::Code,
|
||||
Self::Universal => old,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,8 @@ impl Span {
|
||||
*self = self.join(other)
|
||||
}
|
||||
|
||||
/// Create a new span with n characters inserted inside of this span.
|
||||
/// Create a new span by specifying a span in which a modification happened
|
||||
/// and how many characters are now in that span.
|
||||
pub fn inserted(mut self, other: Self, n: usize) -> Self {
|
||||
if !self.surrounds(other) {
|
||||
panic!();
|
||||
|
Loading…
x
Reference in New Issue
Block a user