Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rendering for textbox and hyperlink #119

Merged
merged 4 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions commons/src/lexer/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ impl AddAssign<Option<SpanLen>> for Position {
}
}

impl AddAssign<usize> for Position {
fn add_assign(&mut self, rhs: usize) {
self.col_utf8 += rhs;
self.col_utf16 += rhs;
self.col_grapheme += rhs;
}
}

impl<T> Add<T> for Position
where
Position: AddAssign<T>,
Expand Down Expand Up @@ -122,13 +130,21 @@ impl SubAssign<SpanLen> for Position {
impl SubAssign<Option<SpanLen>> for Position {
fn sub_assign(&mut self, rhs: Option<SpanLen>) {
if let Some(rhs) = rhs {
self.col_utf8 += rhs.len_utf8;
self.col_utf16 += rhs.len_utf16;
self.col_grapheme += rhs.len_grapheme;
self.col_utf8 -= rhs.len_utf8;
self.col_utf16 -= rhs.len_utf16;
self.col_grapheme -= rhs.len_grapheme;
}
}
}

impl SubAssign<usize> for Position {
fn sub_assign(&mut self, rhs: usize) {
self.col_utf8 -= rhs;
self.col_utf16 -= rhs;
self.col_grapheme -= rhs;
}
}

impl<T> Sub<T> for Position
where
Position: SubAssign<T>,
Expand Down
1 change: 1 addition & 0 deletions inline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ unimarkup-commons = { path = "../commons/", version = "0" }

[dev-dependencies]
unimarkup-commons = { path ="../commons/", version = "0", features = ["test_runner"] }
unimarkup-core = { path = "../core/", version = "0" }
serde.workspace = true
serde_yaml.workspace = true
libtest-mimic = "0.6.1"
10 changes: 5 additions & 5 deletions inline/src/element/formatting/ambiguous.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ fn resolve_closing<'slice, 'input>(
close_token.kind,
inner,
None, // check for optional attributes here
open_token.start,
open_token.start + counterpart(close_token.kind).len(), // Because inner token gets closed
close_token.end,
false,
));
Expand Down Expand Up @@ -271,12 +271,12 @@ fn to_inline<'input>(
inner,
None,
inner_token.start,
inner_token.end,
end - outer_token.kind.len(), // Because the outer token is at "end"
implicit_end,
)],
attributes,
outer_token.start,
outer_token.end,
end,
implicit_end,
)
} else {
Expand Down Expand Up @@ -320,7 +320,7 @@ fn main_part(kind: InlineTokenKind) -> InlineTokenKind {
InlineTokenKind::Italic | InlineTokenKind::BoldItalic => InlineTokenKind::Bold,
InlineTokenKind::Underline => kind,
InlineTokenKind::Subscript | InlineTokenKind::UnderlineSubscript => {
InlineTokenKind::Underline
InlineTokenKind::Subscript
}
_ => kind,
}
Expand All @@ -334,7 +334,7 @@ fn sub_part(kind: InlineTokenKind) -> InlineTokenKind {
InlineTokenKind::Bold | InlineTokenKind::BoldItalic => InlineTokenKind::Italic,
InlineTokenKind::Subscript => kind,
InlineTokenKind::Underline | InlineTokenKind::UnderlineSubscript => {
InlineTokenKind::Subscript
InlineTokenKind::Underline
}
_ => kind,
}
Expand Down
1 change: 1 addition & 0 deletions inline/src/element/textbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub(crate) fn parse<'slice, 'input>(
}
None => {
scoped_parser.iter.rollback(checkpoint);
scoped_parser.iter.next(); // Consume open bracket
}
}

Expand Down
4 changes: 3 additions & 1 deletion inline/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Crate for parsing Unimarkup inline elements.

pub mod element;
pub mod parser;

mod tokenize;

pub mod parser;
pub use tokenize::kind::InlineTokenKind;
18 changes: 3 additions & 15 deletions inline/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ impl<'slice, 'input> InlineParser<'slice, 'input> {
let mut inlines = Vec::default();
let mut format_closes = false;

#[cfg(debug_assertions)]
let mut curr_len = parser.iter.max_len();

parser.iter.reset_peek();

'outer: while let Some(kind) = parser.iter.peek_kind() {
Expand Down Expand Up @@ -142,8 +139,8 @@ impl<'slice, 'input> InlineParser<'slice, 'input> {
let success = parser.iter.rollback(checkpoint);
debug_assert!(
success,
"Rollback was not successful for checkpoint '{:?}'",
checkpoint
"Inline rollback was not successful at '{:?}'",
parser.iter.peek()
)
}
}
Expand All @@ -154,15 +151,6 @@ impl<'slice, 'input> InlineParser<'slice, 'input> {

parser = updated_parser;
inlines = updated_inlines;

#[cfg(debug_assertions)]
{
assert!(
parser.iter.max_len() < curr_len,
"Parser consumed no token in iteration."
);
curr_len = parser.iter.max_len();
}
}

if !format_closes {
Expand Down Expand Up @@ -230,7 +218,7 @@ mod test {

#[test]
fn dummy_for_debugging() {
let tokens = unimarkup_commons::lexer::token::lex_str("`a`");
let tokens = unimarkup_commons::lexer::token::lex_str("[Simple textbox]");
let mut inline_parser = InlineParser {
iter: InlineTokenIterator::from(TokenIterator::from(&*tokens)),
context: InlineContext::default(),
Expand Down
39 changes: 30 additions & 9 deletions inline/src/tokenize/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ impl<'slice, 'input> From<InlineTokenIterator<'slice, 'input>> for TokenIterator
}

impl<'slice, 'input> InlineTokenIterator<'slice, 'input> {
pub fn max_len(&self) -> usize {
self.token_iter.max_len()
}

/// Resets peek to get `peek() == next()`.
///
/// **Note:** Needed to reset peek index after using `peeking_next()`.
Expand Down Expand Up @@ -99,7 +95,8 @@ impl<'slice, 'input> InlineTokenIterator<'slice, 'input> {
}

pub(crate) fn cache_token(&mut self, token: InlineToken<'input>) {
self.cached_token = Some(token)
self.cached_token = Some(token);
self.peeked_cache = false;
}

/// Marks the given format as being open.
Expand Down Expand Up @@ -184,13 +181,21 @@ impl<'slice, 'input> InlineTokenIterator<'slice, 'input> {

/// Creates a checkpoint of the current position in the uderlying [`TokenIterator`].
/// This may be used to `rollback()` to this checkoint at a later point.
pub fn checkpoint(&self) -> Checkpoint<'slice, 'input> {
self.token_iter.checkpoint()
pub fn checkpoint(&self) -> InlineCheckpoint<'slice, 'input> {
InlineCheckpoint {
iter_checkpoint: self.token_iter.checkpoint(),
cached_token: self.cached_token,
updated_prev: self.updated_prev,
peeked_cache: self.peeked_cache,
}
}

/// Rolls back the iterator to the given checkpoint.
pub fn rollback(&mut self, checkpoint: Checkpoint<'slice, 'input>) -> bool {
self.token_iter.rollback(checkpoint)
pub fn rollback(&mut self, checkpoint: InlineCheckpoint<'slice, 'input>) -> bool {
self.cached_token = checkpoint.cached_token;
self.updated_prev = checkpoint.updated_prev;
self.peeked_cache = checkpoint.peeked_cache;
self.token_iter.rollback(checkpoint.iter_checkpoint)
}

/// Skip all tokens until the main index is aligned with the current peek index.
Expand Down Expand Up @@ -277,3 +282,19 @@ impl<'slice, 'input> PeekingNext for InlineTokenIterator<'slice, 'input> {
}
}
}

/// Inline checkpoint to rollback the iterator.
///
/// **Note:** The checkpoint does not include the open formats map.
/// Element parsers must ensure that the open format map remains unchanged if an element could not be parsed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InlineCheckpoint<'slice, 'input> {
/// Checkpoint of the underlying [`TokenIterator`].
iter_checkpoint: Checkpoint<'slice, 'input>,
/// Optional cached token used for splitting ambiguous tokens.
cached_token: Option<InlineToken<'input>>,
/// Optional token in case the previously returned token was changed after being returned by the iterator.
updated_prev: Option<InlineToken<'input>>,
/// Flag to mark if the cached token was viewed when peeking the next token.
peeked_cache: bool,
}
6 changes: 0 additions & 6 deletions inline/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
// mod lexer;
mod parser;
mod snapshot;

// use lexer::test_lexer_snapshots;
use libtest_mimic::Arguments;
use parser::test_parser_snapshots;
pub(crate) use snapshot::*;

fn main() {
let args = Arguments::from_args();
// let lexer_tests = test_lexer_snapshots();
let parser_tests = test_parser_snapshots();

// let mut tests = lexer_tests;
// tests.append(&mut parser_tests);

libtest_mimic::run(&args, parser_tests).exit();
}
41 changes: 35 additions & 6 deletions inline/tests/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::panic;

use crate::snapshot::Snapshot;
use libtest_mimic::Trial;
use unimarkup_commons::test_runner::{self, snap_test_runner::SnapTestRunner};
use unimarkup_commons::test_runner::{self, snap_test_runner::SnapTestRunner, test_file};
use unimarkup_inline::parser::InlineContext;

mod snapshot;
Expand All @@ -17,10 +17,12 @@ pub fn test_parser_snapshots() -> Vec<Trial> {

let mut test_runs = Vec::with_capacity(test_cases.len());
for case in test_cases {
let test_name = format!("{}::{}", module_path!(), case.test.name.as_str());
let spec_test_name = format!("{}::spec::{}", module_path!(), case.test.name.as_str());
let snap_test_name = format!("{}::snap::{}", module_path!(), case.test.name.as_str());

let test_run = move || {
panic::catch_unwind(|| run_test_case(case)).map_err(|err| {
let cloned_case = case.clone();
let spec_test_run = move || {
std::panic::catch_unwind(|| run_spec_test(cloned_case)).map_err(|err| {
let panic_msg = err
.downcast_ref::<&str>()
.unwrap_or(&"Panic message not available");
Expand All @@ -29,13 +31,40 @@ pub fn test_parser_snapshots() -> Vec<Trial> {
})
};

test_runs.push(Trial::test(test_name, test_run));
let snap_test_run = move || {
panic::catch_unwind(|| run_snap_test(case)).map_err(|err| {
let panic_msg = err
.downcast_ref::<&str>()
.unwrap_or(&"Panic message not available");

format!("Test case panicked: {}", panic_msg).into()
})
};

test_runs.push(Trial::test(spec_test_name, spec_test_run));
test_runs.push(Trial::test(snap_test_name, snap_test_run));
}

test_runs
}

fn run_test_case(case: test_runner::test_file::TestCase) {
fn run_spec_test(case: test_runner::test_file::TestCase) {
test_runner::spec_test::assert_um_spec(
&case.file_name,
&case.test,
unimarkup_commons::config::Config::default(),
|test: &test_file::Test, cfg| {
let input = test.input.trim_end();
let um = unimarkup_core::Unimarkup::parse(input, cfg);
test_file::TestOutputs {
html: Some(um.render_html().unwrap().to_string()),
um: Some(test.input.clone()),
}
},
);
}

fn run_snap_test(case: test_runner::test_file::TestCase) {
let tokens = unimarkup_commons::lexer::token::lex_str(&case.test.input);

let runner = SnapTestRunner::with_fn(&case.test.name, &tokens, |slice| {
Expand Down
12 changes: 11 additions & 1 deletion inline/tests/spec/markup/bold.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ tests:
The next ***word** is bolditalic.

html: |
The next <em><strong>word</strong> is bold.</em>
The next <em><strong>word</strong> is bolditalic.</em>

- name: ambiguous-end
description: |
Expand All @@ -94,3 +94,13 @@ tests:

html: |
The next <strong>word</strong>* is bold.

- name: ambiguous-close
description: |
BoldItalic that's closed with an ambiguous token.

input: |
**bold *+italic*** plain

html: |
<strong>bold <em>+italic</em></strong> plain
16 changes: 16 additions & 0 deletions inline/tests/spec/markup/textbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Unimarkup specification version
spec: "0.0.1"

name: textbox
description: Contains tests for the textbox element.

tests:
- name: simple-textbox
description: |
Parse a simple textbox.

input: |
[Simple textbox]

html: |
<span>Simple textbox</span>
27 changes: 27 additions & 0 deletions inline/tests/spec/snapshots/parser/bold/ambiguous-close.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
source: inline/tests/parser/mod.rs
info: "Test 'ambiguous-close' from 'markup\\bold.yml'"
---
Bold @ (1:1)->(1:19) (
Plain @ (1:3)->(1:8) (
bold
^^^^^
)
Italic @ (1:8)->(1:17) (
Plain @ (1:9)->(1:16) (
+italic
^^^^^^^
)
)
)
Plain @ (1:19)->(1:25) (
plain
^^^^^^
)

---
With input:

**bold *+italic*** plain


Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Plain @ (1:1)->(1:10) (
^^^^^^^^^
)
Italic @ (1:10)->(1:34) (
Bold @ (1:10)->(1:19) (
Bold @ (1:11)->(1:19) (
Plain @ (1:13)->(1:17) (
word
^^^^
Expand Down
Loading
Loading