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.
|
/// Parse an atomic primary. Returns `Some` if all of the input was consumed.
|
||||||
pub fn parse_block(source: &str) -> Option<Rc<GreenNode>> {
|
pub fn parse_atomic(source: &str, _: bool) -> Option<Vec<Green>> {
|
||||||
let mut p = Parser::new(source);
|
let mut p = Parser::new(source);
|
||||||
block(&mut p);
|
primary(&mut p, true).ok()?;
|
||||||
if p.eof() {
|
p.eject()
|
||||||
match p.finish().into_iter().next() {
|
}
|
||||||
Some(Green::Node(node)) => Some(node),
|
|
||||||
_ => unreachable!(),
|
/// Parse some markup. Returns `Some` if all of the input was consumed.
|
||||||
}
|
pub fn parse_markup(source: &str, _: bool) -> Option<Vec<Green>> {
|
||||||
} else {
|
let mut p = Parser::new(source);
|
||||||
None
|
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.
|
/// 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.
|
// Line-based markup that is not currently at the start of the line.
|
||||||
NodeKind::Eq | NodeKind::Minus | NodeKind::EnumNumbering(_) => {
|
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.
|
// Hashtag + keyword / identifier.
|
||||||
@ -196,7 +214,7 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
|
|||||||
let marker = p.marker();
|
let marker = p.marker();
|
||||||
|
|
||||||
// Start the unary expression.
|
// 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) => {
|
Some(op) => {
|
||||||
p.eat();
|
p.eat();
|
||||||
let prec = op.precedence();
|
let prec = op.precedence();
|
||||||
@ -268,7 +286,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Structures.
|
// Structures.
|
||||||
Some(NodeKind::LeftParen) => parenthesized(p),
|
Some(NodeKind::LeftParen) => parenthesized(p, atomic),
|
||||||
Some(NodeKind::LeftBracket) => {
|
Some(NodeKind::LeftBracket) => {
|
||||||
template(p);
|
template(p);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -329,7 +347,7 @@ fn literal(p: &mut Parser) -> bool {
|
|||||||
/// - Dictionary literal
|
/// - Dictionary literal
|
||||||
/// - Parenthesized expression
|
/// - Parenthesized expression
|
||||||
/// - Parameter list of closure expression
|
/// - Parameter list of closure expression
|
||||||
fn parenthesized(p: &mut Parser) -> ParseResult {
|
fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult {
|
||||||
let marker = p.marker();
|
let marker = p.marker();
|
||||||
|
|
||||||
p.start_group(Group::Paren);
|
p.start_group(Group::Paren);
|
||||||
@ -344,7 +362,7 @@ fn parenthesized(p: &mut Parser) -> ParseResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Arrow means this is a closure's parameter list.
|
// Arrow means this is a closure's parameter list.
|
||||||
if p.at(&NodeKind::Arrow) {
|
if !atomic && p.at(&NodeKind::Arrow) {
|
||||||
params(p, marker);
|
params(p, marker);
|
||||||
p.eat_assert(&NodeKind::Arrow);
|
p.eat_assert(&NodeKind::Arrow);
|
||||||
return marker.perform(p, NodeKind::Closure, expr);
|
return marker.perform(p, NodeKind::Closure, expr);
|
||||||
@ -507,20 +525,25 @@ fn template(p: &mut Parser) {
|
|||||||
fn block(p: &mut Parser) {
|
fn block(p: &mut Parser) {
|
||||||
p.perform(NodeKind::Block, |p| {
|
p.perform(NodeKind::Block, |p| {
|
||||||
p.start_group(Group::Brace);
|
p.start_group(Group::Brace);
|
||||||
while !p.eof() {
|
expr_list(p);
|
||||||
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(_)));
|
|
||||||
}
|
|
||||||
p.end_group();
|
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.
|
/// Parse a function call.
|
||||||
fn call(p: &mut Parser, callee: Marker) -> ParseResult {
|
fn call(p: &mut Parser, callee: Marker) -> ParseResult {
|
||||||
callee.perform(p, NodeKind::Call, |p| args(p, true, true))
|
callee.perform(p, NodeKind::Call, |p| args(p, true, true))
|
||||||
|
@ -21,6 +21,8 @@ pub struct Parser<'s> {
|
|||||||
groups: Vec<GroupEntry>,
|
groups: Vec<GroupEntry>,
|
||||||
/// The children of the currently built node.
|
/// The children of the currently built node.
|
||||||
children: Vec<Green>,
|
children: Vec<Green>,
|
||||||
|
/// Whether the last group was terminated.
|
||||||
|
last_group_terminated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Parser<'s> {
|
impl<'s> Parser<'s> {
|
||||||
@ -36,6 +38,7 @@ impl<'s> Parser<'s> {
|
|||||||
current_start: 0,
|
current_start: 0,
|
||||||
groups: vec![],
|
groups: vec![],
|
||||||
children: vec![],
|
children: vec![],
|
||||||
|
last_group_terminated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +47,15 @@ impl<'s> Parser<'s> {
|
|||||||
self.children
|
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.
|
/// Create a new marker.
|
||||||
pub fn marker(&mut self) -> Marker {
|
pub fn marker(&mut self) -> Marker {
|
||||||
Marker(self.children.len())
|
Marker(self.children.len())
|
||||||
@ -190,6 +202,11 @@ impl<'s> Parser<'s> {
|
|||||||
self.tokens.scanner().column(index)
|
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.
|
/// Continue parsing in a group.
|
||||||
///
|
///
|
||||||
/// When the end delimiter of the group is reached, all subsequent calls to
|
/// 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");
|
let group = self.groups.pop().expect("no started group");
|
||||||
self.tokens.set_mode(group.prev_mode);
|
self.tokens.set_mode(group.prev_mode);
|
||||||
self.repeek();
|
self.repeek();
|
||||||
|
self.last_group_terminated = true;
|
||||||
|
|
||||||
let mut rescan = self.tokens.mode() != group_mode;
|
let mut rescan = self.tokens.mode() != group_mode;
|
||||||
|
|
||||||
@ -243,6 +261,7 @@ impl<'s> Parser<'s> {
|
|||||||
rescan = false;
|
rescan = false;
|
||||||
} else if required {
|
} else if required {
|
||||||
self.push_error(format_eco!("expected {}", end));
|
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
|
/// Low-level bump that consumes exactly one token without special trivia
|
||||||
/// handling.
|
/// handling.
|
||||||
fn bump(&mut self) {
|
fn bump(&mut self) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::{Not, Range};
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::loading::{FileHash, Loader};
|
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::ast::Markup;
|
||||||
use crate::syntax::{self, Category, GreenNode, NodeKind, RedNode, Span};
|
use crate::syntax::{self, Category, GreenNode, RedNode, Span};
|
||||||
use crate::util::PathExt;
|
use crate::util::PathExt;
|
||||||
|
|
||||||
#[cfg(feature = "codespan-reporting")]
|
#[cfg(feature = "codespan-reporting")]
|
||||||
@ -285,27 +285,10 @@ impl SourceFile {
|
|||||||
|
|
||||||
// Update the root node.
|
// Update the root node.
|
||||||
let insertion_span = Span::new(self.id, replace.start, replace.end);
|
let insertion_span = Span::new(self.id, replace.start, replace.end);
|
||||||
let incremental_target =
|
let source = self.src().to_string();
|
||||||
Rc::make_mut(&mut self.root).incremental_parent(insertion_span);
|
if !Rc::make_mut(&mut self.root).incremental(&source, insertion_span, with.len())
|
||||||
|
{
|
||||||
match incremental_target {
|
self.root = parse(self.src());
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,6 +494,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test inserting at the begining.
|
// 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::Parbreak => Some(MarkupNode::Parbreak),
|
||||||
NodeKind::Strong => Some(MarkupNode::Strong),
|
NodeKind::Strong => Some(MarkupNode::Strong),
|
||||||
NodeKind::Emph => Some(MarkupNode::Emph),
|
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::Escape(c) => Some(MarkupNode::Text((*c).into())),
|
||||||
NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())),
|
NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())),
|
||||||
NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())),
|
NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())),
|
||||||
|
@ -158,6 +158,7 @@ impl Category {
|
|||||||
NodeKind::Space(_) => None,
|
NodeKind::Space(_) => None,
|
||||||
NodeKind::Parbreak => None,
|
NodeKind::Parbreak => None,
|
||||||
NodeKind::Text(_) => None,
|
NodeKind::Text(_) => None,
|
||||||
|
NodeKind::TextInLine(_) => None,
|
||||||
NodeKind::List => None,
|
NodeKind::List => None,
|
||||||
NodeKind::Enum => None,
|
NodeKind::Enum => None,
|
||||||
NodeKind::Array => None,
|
NodeKind::Array => None,
|
||||||
|
@ -15,6 +15,9 @@ pub use span::*;
|
|||||||
use self::ast::{MathNode, RawNode, TypedNode};
|
use self::ast::{MathNode, RawNode, TypedNode};
|
||||||
use crate::diag::Error;
|
use crate::diag::Error;
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngularUnit, LengthUnit};
|
||||||
|
use crate::parse::{
|
||||||
|
parse_atomic, parse_code, parse_markup, parse_markup_elements, TokenMode,
|
||||||
|
};
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -73,6 +76,43 @@ impl Green {
|
|||||||
Self::Token(data) => data.kind = kind,
|
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 {
|
impl Default for Green {
|
||||||
@ -148,60 +188,181 @@ impl GreenNode {
|
|||||||
self.data().len()
|
self.data().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the deepest incremental-safe node and its offset in the source
|
/// The error messages for this node and its descendants.
|
||||||
/// code.
|
pub fn errors(&self) -> Vec<NodeKind> {
|
||||||
pub fn incremental_parent(&mut self, span: Span) -> Option<(&mut GreenNode, usize)> {
|
let mut res = self.children.iter().flat_map(|c| c.errors()).collect::<Vec<_>>();
|
||||||
self.incremental_parent_internal(span, 0)
|
|
||||||
|
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,
|
&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,
|
mut offset: usize,
|
||||||
) -> Option<(&mut GreenNode, usize)> {
|
parent_mode: TokenMode,
|
||||||
for child in self.children.iter_mut() {
|
outermost: bool,
|
||||||
match child {
|
) -> bool {
|
||||||
Green::Token(n) => {
|
let kind = self.kind().clone();
|
||||||
if offset < span.start {
|
let mode = kind.mode().apply(parent_mode);
|
||||||
// the token is strictly before the span
|
eprintln!("in {:?} (mode {:?})", kind, mode);
|
||||||
offset += n.len();
|
|
||||||
} else {
|
let mut loop_result = None;
|
||||||
// the token is within or after the span; tokens are
|
let mut child_at_start = true;
|
||||||
// never safe, so we return.
|
let last = self.children.len() - 1;
|
||||||
return None;
|
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;
|
// This didn't work, so we try to replace the child at this
|
||||||
if offset < span.start && end < span.start {
|
// level.
|
||||||
// the node is strictly before the span
|
let (function, policy) =
|
||||||
offset += n.len();
|
if let Some(p) = child.kind().reparsing_function(mode) {
|
||||||
} else if span.start >= offset
|
p
|
||||||
&& 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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// the node is overlapping or after after the span; nodes are
|
return false;
|
||||||
// never safe, so we return.
|
};
|
||||||
return None;
|
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.
|
/// The error messages for this node and its descendants.
|
||||||
pub fn errors(self) -> Vec<Error> {
|
pub fn errors(self) -> Vec<Error> {
|
||||||
if !self.green.erroneous() {
|
if !self.green.erroneous() {
|
||||||
@ -593,6 +755,8 @@ pub enum NodeKind {
|
|||||||
Parbreak,
|
Parbreak,
|
||||||
/// A consecutive non-markup string.
|
/// A consecutive non-markup string.
|
||||||
Text(EcoString),
|
Text(EcoString),
|
||||||
|
/// A text node that cannot appear at the beginning of a source line.
|
||||||
|
TextInLine(EcoString),
|
||||||
/// A non-breaking space: `~`.
|
/// A non-breaking space: `~`.
|
||||||
NonBreakingSpace,
|
NonBreakingSpace,
|
||||||
/// An en-dash: `--`.
|
/// An en-dash: `--`.
|
||||||
@ -729,19 +893,249 @@ impl NodeKind {
|
|||||||
matches!(self, Self::LeftParen | Self::RightParen)
|
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.
|
/// Whether this is some kind of error.
|
||||||
pub fn is_error(&self) -> bool {
|
pub fn is_error(&self) -> bool {
|
||||||
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
|
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether it is safe to do incremental parsing on this node.
|
/// Whether this node is `at_start` given the previous value of the property.
|
||||||
pub fn is_incremental_safe(&self) -> bool {
|
pub fn is_at_start(&self, prev: bool) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Block | Self::Markup => true,
|
Self::Space(n) if *n > 0 => true,
|
||||||
|
Self::Parbreak => true,
|
||||||
|
Self::LineComment | Self::BlockComment => prev,
|
||||||
_ => false,
|
_ => 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.
|
/// A human-readable name for the kind.
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@ -794,7 +1188,7 @@ impl NodeKind {
|
|||||||
Self::Space(_) => "space",
|
Self::Space(_) => "space",
|
||||||
Self::Linebreak => "forced linebreak",
|
Self::Linebreak => "forced linebreak",
|
||||||
Self::Parbreak => "paragraph break",
|
Self::Parbreak => "paragraph break",
|
||||||
Self::Text(_) => "text",
|
Self::Text(_) | Self::TextInLine(_) => "text",
|
||||||
Self::NonBreakingSpace => "non-breaking space",
|
Self::NonBreakingSpace => "non-breaking space",
|
||||||
Self::EnDash => "en dash",
|
Self::EnDash => "en dash",
|
||||||
Self::EmDash => "em dash",
|
Self::EmDash => "em dash",
|
||||||
@ -855,3 +1249,87 @@ impl Display for NodeKind {
|
|||||||
f.pad(self.as_str())
|
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)
|
*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 {
|
pub fn inserted(mut self, other: Self, n: usize) -> Self {
|
||||||
if !self.surrounds(other) {
|
if !self.surrounds(other) {
|
||||||
panic!();
|
panic!();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user