#[cfg(verus_keep_ghost)]
use vstd::prelude::*;
include!("classify_kernel_macros.rs");
#[path = "classify_kernel_consumers.rs"]
pub(crate) mod consumers;
#[path = "classify_kernel_predicates.rs"]
mod predicates;
use predicates::{
body_starts_with_pipe,
is_atx_heading,
is_blank,
is_blank_from,
is_closing_fence,
is_fence_marker,
is_list_item,
is_setext_underline,
is_table_delimiter,
is_thematic_break,
};
#[derive(Clone, Copy)]
#[cfg_attr(not(verus_keep_ghost), derive(Debug, Eq, PartialEq))]
pub(crate) struct CharIndex(pub(crate) usize);
#[derive(Clone, Copy)]
#[cfg_attr(not(verus_keep_ghost), derive(Debug, Eq, PartialEq))]
pub enum LineClass {
ParagraphText,
AtxHeading,
SetextUnderline,
TableDelimiter,
TableRow,
FenceMarker,
ThematicBreak,
ListItem,
Blank,
Literal,
}
#[derive(Clone, Copy)]
#[cfg_attr(not(verus_keep_ghost), derive(Debug, Eq, PartialEq))]
pub struct OpenFence {
pub marker: char,
pub marker_len: usize,
}
impl OpenFence {
#[must_use]
#[cfg(test)]
pub(crate) const fn new(marker: char, marker_len: usize) -> Self { Self { marker, marker_len } }
}
#[cfg_attr(not(verus_keep_ghost), derive(Clone, Debug, Default, Eq, PartialEq))]
pub struct ClassifyCtxKernel {
pub is_in_fence: bool,
pub open_fence: Option<OpenFence>,
pub previous: Option<LineClass>,
pub prefix_agrees: bool,
}
impl ClassifyCtxKernel {
#[must_use]
#[cfg(test)]
pub(crate) fn in_fence(open_fence: OpenFence) -> Self {
Self {
is_in_fence: true,
open_fence: Some(open_fence),
previous: None,
prefix_agrees: false,
}
}
#[must_use]
pub(crate) const fn following(previous: LineClass, prefix_agrees: bool) -> Self {
Self {
is_in_fence: false,
open_fence: None,
previous: Some(previous),
prefix_agrees,
}
}
}
#[derive(Clone, Copy)]
#[cfg_attr(not(verus_keep_ghost), derive(Debug, Eq, PartialEq))]
pub(crate) struct KernelClassification {
pub(crate) class: LineClass,
pub(crate) body_start: CharIndex,
}
verified_kernel_function! {
#[must_use]
pub(crate) fn classify_seq(chars: &[char], ctx: &ClassifyCtxKernel) -> KernelClassification;
ensures(result =>
result.class == crate::spec_classify(chars@, ctx@),
result.body_start.0 as int == crate::spec_line_parts(chars@).0,
result.body_start.0 <= chars@.len(),
);
{
let (body_start, is_literal) = line_parts(chars);
if ctx.is_in_fence {
if is_literal {
return classified(LineClass::Literal, body_start);
}
return classify_within_fence(chars, body_start, ctx);
}
if is_blank(chars) || is_blank_from(chars, body_start) {
return classified(LineClass::Blank, body_start);
}
if is_literal {
return classified(LineClass::Literal, body_start);
}
classify_open_text(chars, body_start, ctx)
}
}
verified_kernel_function! {
fn classify_within_fence(
chars: &[char],
body_start: usize,
ctx: &ClassifyCtxKernel,
) -> KernelClassification;
requires(body_start <= chars@.len());
ensures(result =>
result.class == crate::spec_within_fence(chars@, body_start as int, ctx@),
result.body_start.0 == body_start,
);
{
match ctx.open_fence {
Some(open) if is_closing_fence(chars, body_start, open) => {
return classified(LineClass::FenceMarker, body_start);
}
Some(_) | None => {}
}
classified(LineClass::Literal, body_start)
}
}
verified_kernel_function! {
fn classify_open_text(
chars: &[char],
body_start: usize,
ctx: &ClassifyCtxKernel,
) -> KernelClassification;
requires(body_start <= chars@.len());
ensures(result =>
result.class == crate::spec_open_text(chars@, body_start as int, ctx@),
result.body_start.0 == body_start,
);
{
if is_fence_marker(chars, body_start) {
return classified(LineClass::FenceMarker, body_start);
}
if is_atx_heading(chars, body_start) {
return classified(LineClass::AtxHeading, body_start);
}
if is_table_delimiter(chars, body_start) {
return classified(LineClass::TableDelimiter, body_start);
}
if body_starts_with_pipe(chars, body_start) {
return classified(LineClass::TableRow, body_start);
}
if matches!(ctx.previous, Some(LineClass::ParagraphText))
&& ctx.prefix_agrees
&& is_setext_underline(chars, body_start)
{
return classified(LineClass::SetextUnderline, body_start);
}
if is_thematic_break(chars, body_start) {
return classified(LineClass::ThematicBreak, body_start);
}
if is_list_item(chars, body_start) {
return classified(LineClass::ListItem, body_start);
}
classified(LineClass::ParagraphText, body_start)
}
}
verified_kernel_function! {
fn classified(class: LineClass, body_start: usize) -> KernelClassification;
ensures(result => result.class == class, result.body_start.0 == body_start);
{
KernelClassification {
class,
body_start: CharIndex(body_start),
}
}
}
verified_loop_function! {
fn line_parts(chars: &[char]) -> (usize, bool);
ensures(result =>
result.0 <= chars@.len(),
result.0 as int == crate::spec_line_parts(chars@).0,
result.1 == crate::spec_line_parts(chars@).1,
);
before {
let (outer_width, mut cursor) = indentation_at(chars, 0, 0);
if outer_width >= 4 {
return (0, true);
}
let mut column = outer_width;
let mut fuel = chars.len() - cursor;
}
while (cursor < chars.len() && fuel > 0 && has_quote_prefix(chars, cursor, column)) invariant(
cursor <= chars@.len(),
column < 4,
fuel >= chars@.len() - cursor,
crate::spec_line_parts_from(chars@, cursor as int, column as int, fuel as nat)
== crate::spec_line_parts(chars@),
) {
let (indent_width, after_indent) = indentation_at(chars, cursor, column);
cursor = after_indent + 1;
column = (column + indent_width + 1) % 4;
if cursor < chars.len() && chars[cursor] == ' ' {
cursor += 1;
column = (column + 1) % 4;
}
fuel -= 1;
}
proof_after {
assert(cursor == chars@.len() || fuel > 0);
let expected = (cursor as int,
crate::spec_indentation_at(chars@, cursor as int, column as int, 0).0 >= 4);
assert(crate::spec_line_parts_from(chars@, cursor as int, column as int, fuel as nat)
== expected);
}
after {
if cursor == chars.len() {
return (cursor, false);
}
let (content_indent, _) = indentation_at(chars, cursor, column);
(cursor, content_indent >= 4)
}
}
verified_kernel_function! {
fn has_quote_prefix(chars: &[char], cursor: usize, column: usize) -> bool;
requires(cursor <= chars@.len(), column < 4);
ensures(result => result == crate::spec_has_quote_prefix(chars@, cursor as int, column as int));
{
let (indent_width, after_indent) = indentation_at(chars, cursor, column);
indent_width < 4 && after_indent < chars.len() && chars[after_indent] == '>'
}
}
verified_loop_function! {
fn indentation_at(chars: &[char], start: usize, column: usize) -> (usize, usize);
requires(start <= chars@.len(), column < 4);
ensures(result =>
result.0 <= 4,
start <= result.1 <= chars@.len(),
result.0 as int == crate::spec_indentation_at(chars@, start as int, column as int, 0).0,
result.1 as int == crate::spec_indentation_at(chars@, start as int, column as int, 0).1,
);
before {
let mut width = 0;
let mut cursor = start;
}
while (cursor < chars.len() && width < 4 && matches!(chars[cursor], ' ' | '\t')) invariant(
start <= cursor <= chars@.len(),
column < 4,
width <= 7,
crate::spec_indentation_at(chars@, cursor as int, column as int, width as int)
== crate::spec_indentation_at(chars@, start as int, column as int, 0),
) {
if chars[cursor] == ' ' {
width += 1;
} else {
width += 4 - ((column + width) % 4);
}
cursor += 1;
}
after {
(if width >= 4 { 4 } else { width }, cursor)
}
}