Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

18 changed files with 507 additions and 2017 deletions

99
Cargo.lock generated
View File

@ -31,42 +31,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "figlet-rs"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01af5c4fc8506204d2280509e9d57d1b41e54e587c185ac09baef30d21ae63ed"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.7"
@ -94,12 +64,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "itertools"
version = "0.10.5"
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "lexpr"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceee0b80e0043f17bf81130471e1b0975179af75fe657af45577d80e2698fe3b"
dependencies = [
"either",
"itoa",
"lexpr-macros",
"proc-macro-hack",
"ryu",
]
[[package]]
name = "lexpr-macros"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd627fb38e19c00d8d068618259205f7a91c91aeade5c15bc35dbca037bb1c35"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
]
[[package]]
@ -108,29 +98,6 @@ version = "0.2.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
[[package]]
name = "logos"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -143,6 +110,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.46"
@ -184,6 +157,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "smawk"
version = "0.3.1"
@ -239,11 +218,7 @@ name = "tml"
version = "0.1.0"
dependencies = [
"ansi_term",
"bitflags",
"figlet-rs",
"fnv",
"itertools",
"logos",
"lexpr",
"strum",
"textwrap",
]

View File

@ -4,11 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
ansi_term = "0.12"
bitflags = "1.3"
figlet-rs = "0.1"
fnv = "1.0"
itertools = "0.10"
logos = "0.12"
lexpr = "0.2"
strum = { version = "0.24", features = ["derive"] }
ansi_term = "0.12"
textwrap = "0.15"

View File

@ -1,8 +0,0 @@
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(box :axis horizontal
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")

View File

@ -1 +0,0 @@
("unstyled" bold "bold" (red "bold red") italic "bold italic")

View File

@ -1,17 +1,6 @@
(
(h1 "Hello world!")
(figlet "Hello figlet!")
(code "
#include <stdio.h>
int main(int argc, const char *argv[]) {
printf(\"Hello, world!\\n\");
return 0;
}
")
(p "Text goes here.")
(h2 "Heading 2")
@ -41,29 +30,6 @@ lines.")
(strikethrough (bold (italic (underline "EVERYTHING AT ONCE"))))
".")
(p
"You can write every color in the "
(red "te")
(green "rm")
(yellow "in")
(blue "al")
" "
(magenta "ra")
(cyan "in")
(white "bo")
(black "w")
".")
(p "To " (bold "be") ", or " (bold "NOT") " to " (bold "be") ": " (italic "that is the question."))
(p "Super long paragraph: According to all known laws of aviation, there is no way that a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible.")
(p "Paragraph with big words:
The implement, the device of my extinction
The terminating clockwork of my gleeful bane
The definitive scourge of its mockery
The end-art instruments lethality attained")
(h3 "Styling " (italic "even ") "works " (bold "in ") (strikethrough "headers."))
(h3 "Super long header: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")

View File

@ -1,95 +0,0 @@
use std::str::FromStr;
use crate::ast::{ParseErrorKind, ParseResult, Value, ValueKind};
use crate::source::WithSource;
#[derive(Clone, Debug, Default)]
pub struct Args(Vec<Arg>);
impl Args {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn contains(&mut self, name: &str) -> bool {
self.0.iter().find(|arg| arg.name.inner == name).is_some()
}
pub fn get(&mut self, name: &str) -> Option<Arg> {
let idx = self.0.iter().position(|old| old.name.inner == name);
idx.map(|idx| self.0.remove(idx))
}
pub fn insert(&mut self, arg: Arg) -> Option<Arg> {
let existing = self
.0
.iter()
.position(|old| old.name.inner == arg.name.inner);
self.0.push(arg);
existing.map(|idx| self.0.remove(idx))
}
}
#[derive(Clone, Debug)]
pub struct Arg {
pub name: WithSource<String>,
pub val: Value,
}
pub trait ParseArgs: Sized {
fn parse_args(args: &mut Args) -> ParseResult<Self>;
}
pub trait ParseValue: Sized {
fn parse_value(val: &Value) -> ParseResult<Self>;
}
pub trait ParseScoped: Sized {
type Default;
fn parse_scoped(args: &mut Args, scope: &str, default: Self::Default) -> ParseResult<Self>;
}
impl<T: ParseValue> ParseScoped for T {
type Default = T;
fn parse_scoped(args: &mut Args, scope: &str, default: Self::Default) -> ParseResult<Self> {
if let Some(arg) = args.get(scope) {
T::parse_value(&arg.val)
} else {
Ok(default)
}
}
}
impl ParseValue for usize {
fn parse_value(val: &Value) -> ParseResult<Self> {
match val.inner {
ValueKind::Number(num) => Ok(num as Self),
_ => Err(val.map(ParseErrorKind::InvalidValue)),
}
}
}
impl<T: ParseValue> ParseValue for Option<T> {
fn parse_value(val: &Value) -> ParseResult<Self> {
if let ValueKind::Symbol(symbol) = &val.inner {
if symbol.to_lowercase() == "none" {
return Ok(None);
}
}
T::parse_value(val).map(|t| Some(t))
}
}
pub(crate) trait ParseSymbol: FromStr {}
impl<T: ParseSymbol> ParseValue for T {
fn parse_value(val: &Value) -> ParseResult<Self> {
match &val.inner {
ValueKind::Symbol(symbol) => {
T::from_str(&symbol).map_err(|_| val.map(ParseErrorKind::InvalidValue))
}
_ => Err(val.map(ParseErrorKind::InvalidValue)),
}
}
}

View File

@ -1,339 +0,0 @@
use std::fmt::{Debug, Display};
use std::ops::Range;
use std::sync::Arc;
use logos::{Lexer, Logos};
use crate::args::{Arg, Args};
use crate::source::{Source, SourcePosition, SourceRange, WithSource};
#[derive(Logos, Copy, Clone, Debug, PartialEq, Eq)]
#[logos(subpattern ident = r"[a-zA-Z][a-zA-Z0-9]*")]
pub enum TokenKind {
#[token("(")]
ParenOpen,
#[token(")")]
ParenClose,
#[regex(r"[a-zA-Z-][a-zA-Z0-9-]*")]
Symbol,
#[regex(r"-?[0-9]*")]
Number,
#[regex(":[a-zA-Z-][a-zA-Z0-9-]*")]
Keyword,
#[regex(r#""([^"\\]|\\t|\\u|\\n|\\")*""#)]
String,
#[regex(r";[^\n]*", logos::skip)]
Comment,
#[token("\n")]
Newline,
#[regex(r"[ \t\f\r]+", logos::skip)]
Whitespace,
#[logos(ignore)]
Eof,
#[error]
Error,
}
#[derive(Debug, Clone)]
pub struct TokenInner {
pub kind: TokenKind,
pub content: String,
}
pub type Token = WithSource<TokenInner>;
#[derive(Debug)]
pub struct AstBuilder<'a> {
lexer: Lexer<'a, TokenKind>,
source: &'a Arc<Source>,
current_row_idx: usize,
current_row_start: usize,
paren_level: usize,
}
impl<'a> AstBuilder<'a> {
pub fn new(source: &'a Arc<Source>) -> Self {
Self {
lexer: TokenKind::lexer(&source.full),
current_row_idx: 0,
current_row_start: 0,
paren_level: 0,
source,
}
}
pub fn idx_to_position(&self, idx: usize) -> SourcePosition {
SourcePosition {
row: self.current_row_idx,
col: idx - self.current_row_start,
}
}
pub fn span_to_range(&self, span: Range<usize>) -> SourceRange {
Range {
start: self.idx_to_position(span.start),
end: self.idx_to_position(span.end),
}
}
pub fn lexer_range(&self) -> SourceRange {
self.span_to_range(self.lexer.span())
}
pub fn with_source<T>(&self, inner: T, range: SourceRange) -> WithSource<T> {
WithSource {
inner,
source: self.source.clone(),
range,
}
}
pub fn token_to_string(&self, token: Token) -> ParseResult<WithSource<String>> {
match token.kind {
TokenKind::String => {
let slice = self.lexer.slice();
let end = slice.len() - 1;
let inner = slice[1..end].to_string();
Ok(WithSource {
inner,
source: token.source,
range: token.range,
})
}
TokenKind::Symbol => Ok(WithSource {
inner: token.content.clone(),
source: token.source,
range: token.range,
}),
TokenKind::Keyword => Ok(WithSource {
inner: token.content[1..].to_string(),
source: token.source,
range: token.range,
}),
_ => Err(token.map(ParseErrorKind::UnexpectedToken(token.kind, None))),
}
}
pub fn next(&mut self) -> ParseResult<Token> {
loop {
let token = self.lexer.next().unwrap_or(TokenKind::Eof);
let span = self.lexer.span();
if let TokenKind::Newline = token {
self.current_row_idx += 1;
self.current_row_start = span.end;
} else if let TokenKind::Error = token {
let range = self.span_to_range(span);
break Err(self.with_source(ParseErrorKind::InvalidToken, range));
} else {
let range = self.span_to_range(span);
if let TokenKind::ParenOpen = token {
self.paren_level += 1;
}
if let TokenKind::ParenClose = token {
if self.paren_level == 0 {
return Err(self.with_source(ParseErrorKind::UnmatchedParen, range));
}
self.paren_level -= 1;
}
if let TokenKind::Eof = token {
if self.paren_level > 0 {
return Err(self.with_source(ParseErrorKind::UnmatchedParen, range));
}
}
break Ok(Token {
inner: TokenInner {
kind: token,
content: self.lexer.slice().to_string(),
},
source: self.source.to_owned(),
range,
});
}
}
}
pub fn expect_next(&mut self) -> ParseResult<Token> {
let tok = self.next()?;
if let TokenKind::Eof = tok.kind {
Err(tok.map(ParseErrorKind::UnexpectedEof))
} else {
Ok(tok)
}
}
pub fn parse_tag_body(&mut self) -> ParseResult<TagBody> {
let mut args = Args::new();
let children = loop {
let token = self.next()?;
match token.kind {
TokenKind::Eof => break vec![],
TokenKind::ParenClose => break vec![],
TokenKind::Keyword => {
let name = self.token_to_string(token.clone())?;
if args.contains(&name) {
return Err(token.map(ParseErrorKind::DuplicateArgument(name.inner)));
}
let val = self.expect_value()?;
args.insert(Arg { name, val });
}
_ => break self.parse_list_from(token)?,
}
};
Ok(TagBody { args, children })
}
pub fn parse_list_from(&mut self, mut token: Token) -> ParseResult<Vec<Value>> {
let mut vals = Vec::new();
loop {
if let TokenKind::Symbol = token.kind {
let start = token.range.start.clone();
let id = self.token_to_string(token)?;
let body = self.parse_tag_body()?;
let end = self.idx_to_position(self.lexer.span().end);
let val = Value {
inner: ValueKind::Tag(Tag { id, body }),
source: self.source.to_owned(),
range: start..end,
};
vals.push(val);
break;
} else if let Some(val) = self.parse_value_from(token)? {
vals.push(val);
token = self.next()?;
} else {
break;
}
}
Ok(vals)
}
pub fn parse_value_from(&mut self, token: Token) -> ParseResult<Option<Value>> {
match token.kind {
TokenKind::Eof | TokenKind::ParenClose => Ok(None),
TokenKind::String => Ok(Some(self.token_to_string(token)?.to_val_string())),
TokenKind::Symbol => Ok(Some(self.token_to_string(token)?.to_val_symbol())),
TokenKind::Number => {
if let Ok(val) = token.content.parse() {
Ok(Some(token.map(ValueKind::Number(val))))
} else {
Err(token.map(ParseErrorKind::BadNumber))
}
}
TokenKind::ParenOpen => {
let start = token.range.end;
let next = self.expect_next()?;
let vals = self.parse_list_from(next)?;
let end = self.idx_to_position(self.lexer.span().end);
Ok(Some(Value {
inner: ValueKind::List(vals),
source: self.source.to_owned(),
range: start..end,
}))
}
_ => Err(token.map(ParseErrorKind::UnexpectedToken(token.kind, None))),
}
}
pub fn expect_value(&mut self) -> ParseResult<Value> {
let next = self.expect_next()?;
self.parse_value_from(next)?
.ok_or_else(|| self.with_source(ParseErrorKind::UnexpectedEof, self.lexer_range()))
}
}
#[derive(Clone, Debug)]
pub enum ValueKind {
String(String),
Number(i32),
Symbol(String),
Tag(Tag),
List(Vec<Value>),
}
pub type Value = WithSource<ValueKind>;
impl WithSource<String> {
pub fn to_val_string(self) -> Value {
Value {
inner: ValueKind::String(self.inner),
source: self.source,
range: self.range,
}
}
pub fn to_val_symbol(self) -> Value {
Value {
inner: ValueKind::Symbol(self.inner),
source: self.source,
range: self.range,
}
}
}
#[derive(Clone, Debug)]
pub struct Tag {
pub id: WithSource<String>,
pub body: TagBody,
}
#[derive(Clone, Debug, Default)]
pub struct TagBody {
pub args: Args,
pub children: Vec<Value>,
}
pub type ParseError = WithSource<ParseErrorKind>;
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseErrorKind {
InvalidToken,
DuplicateArgument(String),
MissingArgument(String),
UnmatchedParen,
UnexpectedToken(TokenKind, Option<TokenKind>),
UnexpectedEof,
InvalidValue,
BadNumber,
}
impl Display for ParseErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Parse error: ")?;
use ParseErrorKind::*;
match self {
InvalidToken => write!(f, "invalid token"),
DuplicateArgument(name) => write!(f, "duplicate {} argument", name),
MissingArgument(name) => write!(f, "missing {} argument", name),
UnmatchedParen => write!(f, "unmatched parentheses"),
UnexpectedToken(kind, Some(expected)) => {
write!(f, "unexpected {:?} token (expected {:?})", kind, expected)
}
UnexpectedToken(kind, None) => write!(f, "unexpected token {:?}", kind),
UnexpectedEof => write!(f, "unexpected end-of-file"),
InvalidValue => write!(f, "invalid value"),
BadNumber => write!(f, "badly-formed number"),
}
}
}

View File

@ -1,117 +0,0 @@
use strum::EnumString;
use crate::args::{Args, ParseArgs, ParseScoped};
use crate::ast::ParseResult;
use crate::border::BorderKind;
use crate::layout::{AxisSize, Dimensions, Margin, Rect, Axis};
use crate::text::{Color, TextLayout};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BlockTagKind {
Section,
P,
Figlet,
Code,
Ul,
Ol,
Li,
Box,
}
impl BlockTagKind {
pub fn children(&self) -> AllowedChildren {
use BlockTagKind::*;
match self {
Section => AllowedChildren::Block,
P => AllowedChildren::TaggedText,
Figlet | Code => AllowedChildren::PlainText,
Ul | Ol => AllowedChildren::Specific(&[Li, Ul, Ol]),
Li => AllowedChildren::TaggedText,
Box => AllowedChildren::Block,
}
}
pub fn make_text_layout(&self, text: String) -> TextLayout {
use BlockTagKind::*;
match self {
Figlet => {
let font = figlet_rs::FIGfont::standard().unwrap();
let figure = font.convert(&text).expect("Failed to FIG-ify text");
TextLayout::from_plain(&figure.to_string())
}
Code => TextLayout::from_plain(&text),
_ => unimplemented!("Text layout for {:?} tag", self),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AllowedChildren {
Block,
PlainText,
TaggedText,
Specific(&'static [BlockTagKind]),
}
impl AllowedChildren {
pub fn allows_styled_text(&self) -> bool {
use AllowedChildren::*;
match self {
TaggedText => true,
_ => false,
}
}
pub fn allows_block(&self, kind: &BlockTagKind) -> bool {
use AllowedChildren::*;
match self {
Block => true,
Specific(allowed) => allowed.contains(kind),
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub struct Block {
pub size: Dimensions<AxisSize>,
pub margin: Rect<Margin>,
pub padding: Rect<usize>,
pub border: Rect<Option<BorderKind>>,
pub border_color: Rect<Color>,
}
impl ParseArgs for Block {
fn parse_args(args: &mut Args) -> ParseResult<Self> {
let size = Dimensions::parse(args, 1)?;
let margin = Rect::parse_scoped(args, "margin", Margin::Fixed(1))?;
let padding = Rect::parse_scoped(args, "padding", 0)?;
let border = Rect::parse_scoped(args, "border", None)?;
let border_color = Rect::parse_scoped(args, "border-color", Color::White)?;
Ok(Self {
size,
margin,
padding,
border,
border_color,
})
}
}
impl Block {
pub fn required_axis_size(&self, axis: Axis) -> AxisSize {
let mut size = self.size.axis(axis).to_owned();
let (ps, pe) = self.padding.axis_sides(axis);
size.add_size(*ps);
size.add_size(*pe);
let (ms, me) = self.margin.axis_sides(axis);
size.add_margin(ms);
size.add_margin(me);
size
}
}

View File

@ -1,9 +0,0 @@
use strum::EnumString;
#[derive(Copy, Clone, Debug, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BorderKind {
Single,
}
impl crate::args::ParseSymbol for BorderKind {}

View File

@ -1,334 +1,6 @@
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::tag::BlockTag;
use crate::args::{Args, ParseArgs, ParseScoped};
use crate::ast::{self, ParseErrorKind, ParseResult, TagBody};
use crate::block::{AllowedChildren, Block, BlockTagKind};
use crate::layout::{Axis, BoxLayout, Dimensions, RenderedBlock};
use crate::node::{NodeId, NodeStore};
use crate::source::WithSource;
use crate::text::{TaggedText, TextBlock, TextLayout, TextParseErrorKind, TextStyle};
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Hash)]
pub struct Dom {
pub root: NodeId,
pub nodes: NodeStore,
}
impl Dom {
pub fn parse(body: &TagBody) -> DomResult<Self> {
let mut builder = DomBuilder::default();
let root = builder.new_node();
let mut block = BoxBuilder::root(root);
let mut text_style = TextStyle::default();
let mut state = DomWalkState {
builder: &mut builder,
box_builder: &mut block,
text_style: &mut text_style,
};
state.add_children(&body.children)?;
block.build(&mut builder);
Ok(Self {
root,
nodes: builder.node_store,
})
}
pub fn print(&self) {
self.print_from(self.root, 0);
}
pub fn print_from(&self, node: NodeId, indent: usize) {
let margin: String = std::iter::repeat(" ").take(indent * 4).collect();
if let Some(NodeChildren(children)) = self.nodes.get(node) {
println!("{}{}: {:?}", margin, node, children);
for child in children.iter() {
self.print_from(*child, indent + 1);
}
} else {
let text = self.nodes.get::<TextBlock>(node);
println!("{}{}: {:?}", margin, node, text);
}
}
pub fn render(&mut self, size: Dimensions<usize>) -> &RenderedBlock {
self.render_from(self.root, size);
self.nodes.get(self.root).unwrap()
}
pub fn render_from(&mut self, node: NodeId, size: Dimensions<usize>) {
let rendered = if let Some(NodeChildren(children)) = self.nodes.get(node) {
let children = children.to_owned();
let axis: Axis = self.nodes.get::<Axis>(node).unwrap().to_owned();
let layout = BoxLayout {
axis,
children: self.get_many::<Block>(&children),
};
let sizes = layout.calc_sizes(size).to_owned();
drop(layout);
for (size, child) in sizes.into_iter().zip(children) {
self.render_from(child, size);
}
let children = self.get_children(node);
let rendered = self.get_many::<RenderedBlock>(&children);
let layout = BoxLayout {
axis,
children: self.get_many::<Block>(&children),
};
layout.render(&rendered)
} else {
let style = TextStyle::default();
let layout: TextLayout = if let Some(layout) = self.nodes.get::<TextLayout>(node) {
layout.to_owned()
} else if let Some(block) = self.nodes.get::<TextBlock>(node) {
block.layout(&style, size.width)
} else {
panic!("Couldn't find text info on node {}", node);
};
let rendered = layout.render(&style);
let block: &Block = self.nodes.get(node).unwrap();
rendered.add_padding(&block.padding)
};
self.nodes.insert(node, rendered);
}
pub fn get_children(&self, node: NodeId) -> Vec<NodeId> {
self.nodes.get::<NodeChildren>(node).unwrap().0.to_owned()
}
pub fn get_many<T: 'static>(&self, children: &[NodeId]) -> Vec<&T> {
children
.iter()
.map(|child| self.nodes.get(*child).unwrap())
.collect()
}
}
#[derive(Clone, Debug, Default)]
pub struct NodeChildren(Vec<NodeId>);
#[derive(Copy, Clone, Debug, Default)]
pub struct NodeParent(NodeId);
#[derive(Debug, Default)]
pub struct DomBuilder {
pub node_store: NodeStore,
pub next_node: NodeId,
}
impl Deref for DomBuilder {
type Target = NodeStore;
fn deref(&self) -> &NodeStore {
&self.node_store
}
}
impl DerefMut for DomBuilder {
fn deref_mut(&mut self) -> &mut NodeStore {
&mut self.node_store
}
}
impl DomBuilder {
pub fn new_node(&mut self) -> NodeId {
let node = self.next_node;
self.next_node += 1;
node
}
}
#[derive(Debug)]
pub struct BoxBuilder {
pub id: NodeId,
pub axis: Axis,
pub children: Vec<NodeId>,
}
impl BoxBuilder {
pub fn new(id: NodeId, args: &mut Args) -> ParseResult<Self> {
Ok(Self {
id,
axis: Axis::parse_scoped(args, "axis", Axis::Vertical)?,
children: Vec::new(),
})
}
pub fn root(id: NodeId) -> Self {
Self {
id,
axis: Axis::Vertical,
children: Vec::new(),
}
}
pub fn build(self, dom: &mut DomBuilder) -> NodeId {
for child in self.children.iter() {
dom.insert(*child, NodeParent(self.id));
}
let children = NodeChildren(self.children);
dom.insert(self.id, children);
dom.insert(self.id, self.axis);
self.id
}
}
#[derive(Debug)]
pub struct DomWalkState<'a> {
pub builder: &'a mut DomBuilder,
pub box_builder: &'a mut BoxBuilder,
pub text_style: &'a mut TextStyle,
}
impl<'a> DomWalkState<'a> {
pub fn fork(&mut self) -> DomWalkState {
DomWalkState {
builder: &mut *self.builder,
box_builder: &mut *self.box_builder,
text_style: &mut *self.text_style,
}
}
pub fn add_text_block(&mut self, kind: BlockTagKind, text: TaggedText) -> NodeId {
let node = self.builder.new_node();
let block = text.make_text_block(kind);
self.builder.insert(node, block);
node
}
pub fn add_block_tag(&mut self, kind: BlockTagKind, body: &ast::TagBody) -> DomResult<NodeId> {
let node = self.builder.new_node();
let mut args = body.args.clone();
let block = Block::parse_args(&mut args).map_err(|e| e.map(e.inner.clone().into()))?;
println!("{:#?}", block);
self.builder.insert(node, block);
if let BlockTagKind::Box = kind {
self.box_builder.children.push(node);
let mut box_builder =
BoxBuilder::new(node, &mut args).map_err(|e| e.map(e.inner.clone().into()))?;
let mut state = DomWalkState {
box_builder: &mut box_builder,
..self.fork()
};
state.add_children(&body.children)?;
box_builder.build(&mut self.builder);
return Ok(node);
}
match kind.children() {
AllowedChildren::TaggedText => {
let text = TaggedText::parse(&self.text_style, &body.children)
.map_err(|e| e.map(e.inner.clone().into()))?;
let block = text.make_text_block(kind);
self.builder.insert(node, block);
self.box_builder.children.push(node);
}
AllowedChildren::PlainText => {
let text = crate::text::parse_plain(&body.children)
.map_err(|e| e.map(e.inner.clone().into()))?;
let layout = kind.make_text_layout(text);
self.builder.insert(node, layout);
self.box_builder.children.push(node);
}
_ => {
self.add_children(&body.children)?;
}
}
Ok(node)
}
pub fn add_tag(mut self, tag: &ast::Tag) -> DomResult<()> {
if let Ok(kind) = BlockTagKind::from_str(&tag.id) {
self.add_block_tag(kind, &tag.body)?;
Ok(())
} else {
Err(tag.id.map(DomErrorKind::InvalidTag))
}
}
pub fn add_children(&mut self, vals: &[ast::Value]) -> DomResult<()> {
for child in vals.iter() {
self.add_child(child)?;
}
Ok(())
}
pub fn add_child(&mut self, val: &ast::Value) -> DomResult<()> {
use ast::ValueKind::*;
match &val.inner {
Tag(tag) => {
let state = self.fork();
state.add_tag(tag)
}
List(list) => self.add_children(list),
String(_string) => {
println!("{}", val.map("Insert string code here:"));
Ok(())
}
_ => Err(val.map(DomErrorKind::InvalidChild)),
}
}
}
pub type DomResult<T> = Result<T, WithSource<DomErrorKind>>;
#[derive(Clone, Debug)]
pub enum DomErrorKind {
InvalidTag,
InvalidChild,
ParseError(ParseErrorKind),
TextError(TextParseErrorKind),
}
impl From<ParseErrorKind> for DomErrorKind {
fn from(e: ParseErrorKind) -> Self {
DomErrorKind::ParseError(e)
}
}
impl From<TextParseErrorKind> for DomErrorKind {
fn from(e: TextParseErrorKind) -> Self {
DomErrorKind::TextError(e)
}
}
impl std::fmt::Display for DomErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use DomErrorKind::*;
if let TextError(e) = &self {
return e.fmt(f);
}
if let ParseError(e) = &self {
return e.fmt(f);
}
write!(f, "DOM error: ")?;
match self {
InvalidTag => write!(f, "invalid tag"),
InvalidChild => write!(f, "invalid child"),
_ => unimplemented!(),
}
}
pub tags: Vec<BlockTag>,
}

View File

@ -1,479 +1,184 @@
use itertools::Itertools;
use strum::EnumString;
use crate::style::Stylesheet;
use crate::tag::*;
use crate::args::*;
use crate::ast::{ParseErrorKind, ParseResult, Value, ValueKind};
use crate::block::Block;
use crate::source::WithSource;
#[derive(Clone, Debug)]
pub struct RenderedBlock {
pub lines: Vec<String>,
pub size: Dimensions<usize>,
}
impl RenderedBlock {
pub fn add_padding(&self, padding: &Rect<usize>) -> Self {
let make_spaces = |num: usize| -> String { std::iter::repeat(" ").take(num).collect() };
let new_width = self.size.width + padding.l + padding.r;
let left_pad = make_spaces(padding.l);
let right_pad = make_spaces(padding.r);
let vertical_pad = make_spaces(new_width);
let mut lines = Vec::new();
lines.extend(std::iter::repeat(vertical_pad.clone()).take(padding.t));
lines.extend(
self.lines
.iter()
.map(|line| format!("{}{}{}", left_pad, line, right_pad)),
);
lines.extend(std::iter::repeat(vertical_pad.clone()).take(padding.b));
Self {
lines,
size: Dimensions {
width: new_width,
height: self.size.height + padding.t + padding.b,
},
}
}
pub fn make_blank(size: Dimensions<usize>) -> Self {
let line: String = std::iter::repeat(" ").take(size.width).collect();
Self {
lines: std::iter::repeat(line).take(size.height).collect(),
size,
}
}
pub fn concat(mut self, axis: Axis, mut other: Self) -> Self {
match axis {
Axis::Vertical => Self {
size: Dimensions {
width: self.size.width.max(other.size.width),
height: self.size.height + other.size.height,
},
lines: self.lines.into_iter().chain(other.lines).collect(),
},
Axis::Horizontal => {
if self.size.height < other.size.height {
let padding = Dimensions {
width: self.size.width,
height: other.size.height - self.size.height,
};
let padding = Self::make_blank(padding);
self = self.concat(Axis::Vertical, padding);
}
if other.size.height < self.size.height {
let padding = Dimensions {
width: other.size.width,
height: self.size.height - other.size.height,
};
let padding = Self::make_blank(padding);
other = other.concat(Axis::Vertical, padding);
}
Self {
size: Dimensions {
width: self.size.width + other.size.width,
height: self.size.height.max(other.size.height),
},
lines: self
.lines
.into_iter()
.zip(other.lines)
.map(|(first, other)| format!("{}{}", first, other))
.collect(),
}
}
}
}
}
#[derive(Copy, Clone, Debug, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum Axis {
Horizontal,
Vertical,
}
impl ParseSymbol for Axis {}
impl Axis {
pub fn cross(&self) -> Self {
match self {
Axis::Horizontal => Axis::Vertical,
Axis::Vertical => Axis::Horizontal,
}
}
}
#[derive(Clone, Debug)]
pub struct BoxLayout<'a> {
pub axis: Axis,
pub children: Vec<&'a Block>,
}
impl<'a> BoxLayout<'a> {
pub fn calc_sizes(&self, available: Dimensions<usize>) -> Vec<Dimensions<usize>> {
let cross = self.axis.cross();
let sizes = self
.children
.iter()
.map(|block| {
let available = *available.axis(cross);
let required = block.required_axis_size(cross);
let min_size = required.min_size();
let remaining = available - min_size.min(available);
let cross_size = block.size.axis(cross).clamp(remaining);
let axis_size = block.size.axis(self.axis).min_size();
match self.axis {
Axis::Horizontal => Dimensions {
width: axis_size,
height: cross_size,
},
Axis::Vertical => Dimensions {
width: cross_size,
height: axis_size,
},
}
})
.collect();
sizes
}
pub fn render(&self, blocks: &[&RenderedBlock]) -> RenderedBlock {
let axis = self.axis;
let cross = axis.cross();
let cross_size = blocks
.iter()
.zip(&self.children)
.map(|(rendered, block)| {
let (ms, me) = block.margin.axis_sides(cross);
ms.required_size() + rendered.size.axis(cross) + me.required_size()
})
.max()
.unwrap_or(0);
let make_spacer = |size: usize| -> RenderedBlock {
let (width, height) = match axis {
Axis::Horizontal => (size, cross_size),
Axis::Vertical => (cross_size, size),
};
RenderedBlock::make_blank(Dimensions { width, height })
};
let mut spacers = Vec::new();
let mut last_margin = 0;
for block in self.children.iter() {
let (ms, me) = block.margin.axis_sides(axis);
let size = ms.required_size().max(last_margin);
spacers.push(make_spacer(size));
last_margin = me.required_size();
}
spacers.push(make_spacer(last_margin));
let mut padded = Vec::new();
for (block, rendered) in self.children.iter().zip(blocks) {
let (ms, me) = block.margin.axis_sides(cross);
let available = cross_size - rendered.size.axis(cross).min(&cross_size);
let (ms, me) = Self::calc_margins(ms, me, available);
let padding = match axis {
Axis::Horizontal => Rect {
l: 0,
r: 0,
t: ms,
b: me,
},
Axis::Vertical => Rect {
t: 0,
b: 0,
l: ms,
r: me,
},
};
padded.push(rendered.add_padding(&padding));
}
spacers
impl TextTag {
pub fn layout<'a>(
&self,
style: &Stylesheet,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style(style);
textwrap::wrap(&styled, options)
.into_iter()
.interleave(padded)
.reduce(|r1, r2| r1.concat(axis, r2))
.unwrap()
.map(|s| s.to_string())
.collect()
}
pub fn calc_margins(start: &Margin, end: &Margin, available: usize) -> (usize, usize) {
let split_available = |available: usize| -> (usize, usize) {
if available & 2 == 0 {
(available >> 1, available >> 1)
} else {
(available >> 1, (available >> 1) | 1)
}
};
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
use Margin::*;
match (start, end) {
(Auto, Auto) => split_available(available),
(Fixed(start), Auto) => (*start, available - start),
(Auto, Fixed(end)) => (available - end, *end),
(Fixed(fixed_start), Fixed(fixed_end)) => {
let taken = fixed_start + fixed_end;
let available = available - taken.min(available);
let (start, end) = split_available(available);
(fixed_start + start, fixed_end + end)
}
}
}
}
#[derive(Clone, Debug)]
pub enum AxisSize {
Fixed(usize),
Bounded(usize, Option<usize>),
}
impl ParseScoped for AxisSize {
type Default = usize;
fn parse_scoped(args: &mut Args, name: &str, mut min: usize) -> ParseResult<Self> {
if let Some(arg) = args.get(name) {
Ok(AxisSize::Fixed(ParseValue::parse_value(&arg.val)?))
} else {
let min_name = format!("min-{}", name);
if let Some(arg) = args.get(&min_name) {
min = ParseValue::parse_value(&arg.val)?;
}
let max_name = format!("max-{}", name);
let max = if let Some(arg) = args.get(&max_name) {
Some(ParseValue::parse_value(&arg.val)?)
} else {
None
for child in self.children.iter() {
let child_string = match child {
TextNode::Tag(tag) => tag.style(style),
TextNode::Text(string) => string.to_owned(),
};
Ok(AxisSize::Bounded(min, max))
string.push_str(&child_string);
}
}
}
impl AxisSize {
pub fn add_size(&mut self, size: usize) {
match self {
AxisSize::Fixed(fixed) => *fixed += size,
AxisSize::Bounded(min, None) => *min += size,
AxisSize::Bounded(min, Some(max)) => {
*min += size;
*max += size;
}
}
string
}
pub fn add_margin(&mut self, margin: &Margin) {
match margin {
Margin::Fixed(margin) => self.add_size(*margin),
Margin::Auto => {}
}
}
pub fn style(&self, style: &Stylesheet) -> String {
let string = self.style_children(style);
pub fn clamp(&self, size: usize) -> usize {
match *self {
AxisSize::Fixed(fixed) => fixed,
AxisSize::Bounded(min, None) => size.max(min),
AxisSize::Bounded(min, Some(max)) => size.max(min).min(max),
}
}
pub fn min_size(&self) -> usize {
match self {
AxisSize::Fixed(fixed) => *fixed,
AxisSize::Bounded(min, _) => *min,
}
}
}
#[derive(Clone, Debug)]
pub enum Margin {
Auto,
Fixed(usize),
}
impl ParseValue for Margin {
fn parse_value(val: &Value) -> ParseResult<Self> {
match &val.inner {
ValueKind::Symbol(symbol) if symbol.to_lowercase() == "auto" => Ok(Margin::Auto),
_ => Ok(Margin::Fixed(usize::parse_value(val)?)),
}
}
}
impl Margin {
pub fn required_size(&self) -> usize {
match self {
Margin::Auto => 0,
Margin::Fixed(fixed) => *fixed,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Dimensions<T> {
pub width: T,
pub height: T,
}
impl<T: ParseScoped> Dimensions<T>
where
T::Default: Clone,
{
pub fn parse(args: &mut Args, default: T::Default) -> ParseResult<Self> {
let width = T::parse_scoped(args, "width", default.clone())?;
let height = T::parse_scoped(args, "height", default)?;
Ok(Self { width, height })
}
}
impl<T> Dimensions<T> {
pub fn axis(&self, axis: Axis) -> &T {
match axis {
Axis::Horizontal => &self.width,
Axis::Vertical => &self.height,
}
}
}
impl Dimensions<AxisSize> {
pub fn min_size(&self) -> Dimensions<usize> {
Dimensions {
width: self.width.min_size(),
height: self.height.min_size(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Rect<T> {
pub l: T,
pub t: T,
pub r: T,
pub b: T,
}
impl<T> Rect<T> {
pub fn map<O>(&self, f: impl Fn(&T) -> O) -> Rect<O> {
Rect::<O> {
l: f(&self.l),
t: f(&self.t),
r: f(&self.r),
b: f(&self.b),
}
}
pub fn axis_sides(&self, axis: Axis) -> (&T, &T) {
match axis {
Axis::Horizontal => (&self.l, &self.r),
Axis::Vertical => (&self.t, &self.b),
}
}
}
impl<T: Clone + ParseValue> Rect<T> {
pub fn parse_shorthand(parsed: WithSource<&[T]>) -> ParseResult<Self> {
match parsed.len() {
1 => Ok(Self {
l: parsed[0].clone(),
t: parsed[0].clone(),
r: parsed[0].clone(),
b: parsed[0].clone(),
}),
2 => Ok(Self {
t: parsed[0].clone(),
b: parsed[0].clone(),
l: parsed[1].clone(),
r: parsed[1].clone(),
}),
3 => Ok(Self {
t: parsed[0].clone(),
l: parsed[1].clone(),
r: parsed[1].clone(),
b: parsed[2].clone(),
}),
4 => Ok(Self {
t: parsed[0].clone(),
r: parsed[1].clone(),
b: parsed[2].clone(),
l: parsed[3].clone(),
}),
_ => Err(parsed.map(ParseErrorKind::InvalidValue)),
}
}
}
impl<T: Clone + ParseValue> ParseScoped for Rect<T> {
type Default = T;
fn parse_scoped(args: &mut Args, name: &str, default: T) -> ParseResult<Self> {
let mut get_side = |side: &str| -> Option<Value> {
let name = format!("{}-{}", name, side);
args.get(&name).map(|arg| arg.val)
use TextTagKind::*;
let painter = match self.kind {
Plain => return string,
Underline => style.underline,
Bold => style.bold,
Italic => style.italic,
Strikethrough => style.strikethrough,
};
let l = get_side("left");
let t = get_side("top");
let r = get_side("right");
let b = get_side("bottom");
if let Some(short) = args.get(name) {
if l.is_some() || t.is_some() || r.is_some() || b.is_some() {
let e = ParseErrorKind::DuplicateArgument(name.to_string());
return Err(short.name.map(e));
}
match &short.val.inner {
ValueKind::List(vals) => {
let mut parsed = Vec::new();
for val in vals.iter() {
parsed.push(T::parse_value(val)?);
}
Self::parse_shorthand(short.val.map(&parsed))
}
_ => {
let val = T::parse_value(&short.val)?;
Self::parse_shorthand(short.val.map(&[val]))
}
}
} else {
let parse_value = |val: Option<Value>| -> ParseResult<T> {
if let Some(val) = val {
T::parse_value(&val)
} else {
Ok(default.clone())
}
};
Ok(Self {
l: parse_value(l)?,
t: parse_value(t)?,
r: parse_value(r)?,
b: parse_value(b)?,
})
}
painter.paint(string).to_string()
}
}
impl BlockTag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
use BlockTagKind::*;
match self.kind {
H1 | H2 | H3 => {
const INDENT: usize = 4;
let styled = self.style_text_children(style);
let bullets = match self.kind {
H1 => "# ",
H2 => "## ",
H3 => "### ",
_ => unreachable!(),
};
let indent: String = std::iter::repeat(" ").take(INDENT).collect();
let options = textwrap::Options::new(available_width)
.initial_indent(&bullets)
.subsequent_indent(&indent);
let lines = textwrap::wrap(&styled, options);
let mut max_len = 0;
lines
.iter()
.enumerate()
.map(|(index, line)| {
let mut line = line.to_string();
max_len = max_len.max(line.len());
let painter = if index == lines.len() - 1 {
while line.len() < max_len {
line.push(' ');
}
&style.header_style_floor
} else {
&style.header_style_float
};
painter.paint(line).to_string()
})
.collect()
}
P => self.layout_text_children(style, available_width),
Ul => self.layout_list_items(style, available_width, false),
Ol => self.layout_list_items(style, available_width, true),
Li | Tr | Td => panic!("{:?} tag cannot be directly laid out", self.kind),
_ => unimplemented!(),
}
}
pub fn layout_list_items(
&self,
style: &Stylesheet,
available_width: usize,
ordered: bool,
) -> Vec<String> {
let indent = style.list_indent;
let mut lines = Vec::new();
let mut index = 1;
let spacer: String = std::iter::repeat(" ").take(indent).collect();
let prefix = format!("{:>width$} ", style.ul_prefix, width = indent - 2);
let mut prefix = style.bullet.paint(prefix).to_string();
let available_width = if available_width > indent {
available_width - indent
} else {
0
};
for child in self.children.iter() {
if ordered {
prefix = format!("{:>width$}. ", index, width = indent - 2);
prefix = style.bullet.paint(prefix).to_string();
}
let options = textwrap::Options::new(available_width)
.initial_indent(&prefix)
.subsequent_indent(&spacer);
match child {
BlockNode::Text(tag) => {
index += 1;
let mut li = tag.layout(style, options);
lines.append(&mut li);
}
BlockNode::Block(tag) => {
let li = match tag.kind {
BlockTagKind::Ol => tag.layout_list_items(style, available_width, true),
BlockTagKind::Ul => tag.layout_list_items(style, available_width, false),
BlockTagKind::Li => {
index += 1;
let mut li = tag.layout_text_children(style, options);
lines.append(&mut li);
continue;
}
_ => panic!("Unexpected {:?} in {:?}", tag.kind, self.kind),
};
lines.extend(li.into_iter().map(|line| format!("{}{}", spacer, line)));
}
BlockNode::Leaf(_) => panic!("Unexpected leaf tag in {:?}", self.kind),
}
}
lines
}
pub fn layout_text_children<'a>(
&self,
style: &Stylesheet,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style_text_children(style);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_text_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
match child {
BlockNode::Text(tag) => string.push_str(&tag.style(style)),
_ => panic!("{:?} node attempted to style a non-text child", self.kind),
}
}
string
}
}

View File

@ -1,40 +1,32 @@
pub mod args;
pub mod ast;
pub mod block;
pub mod border;
use std::str::FromStr;
use strum::EnumString;
pub mod dom;
pub mod layout;
pub mod node;
pub mod source;
pub mod text;
pub mod parse;
pub mod style;
pub mod tag;
use style::Stylesheet;
fn main() {
let src_path = std::env::args().nth(1).unwrap();
let src = std::fs::read_to_string(src_path).unwrap();
let options =
lexpr::parse::Options::new().with_string_syntax(lexpr::parse::StringSyntax::Elisp);
let expr = lexpr::from_str_custom(&src, options).unwrap();
let source = std::sync::Arc::new(source::Source::from_string(src.clone()));
let mut ast_builder = ast::AstBuilder::new(&source);
let body = match ast_builder.parse_tag_body() {
Ok(body) => body,
Err(e) => panic!("Parse error:\n{}", e),
};
println!("{:#?}", expr);
println!("{:#?}", body);
let dom = parse::Parser::parse(&expr);
let style = Stylesheet::default();
let width = 40;
let mut dom = match dom::Dom::parse(&body) {
Ok(dom) => dom,
Err(e) => panic!("DOM parse error:\n{}", e),
};
for tag in dom.tags.iter() {
for line in tag.layout(&style, width) {
println!("{}", line);
}
let size = layout::Dimensions {
width: 80,
height: 20,
};
dom.print();
let rendered = dom.render(size);
for line in rendered.lines.iter() {
println!("{}", line);
println!("");
}
}

View File

@ -1,80 +0,0 @@
use std::any::{Any, TypeId};
use std::fmt::Debug;
use fnv::FnvHashMap as HashMap;
pub type NodeId = u32;
pub type ComponentStorage<T> = HashMap<NodeId, T>;
#[derive(Debug, Default)]
pub struct NodeStore {
storage: HashMap<TypeId, Box<dyn ComponentStorageTrait>>,
}
impl NodeStore {
pub fn get_storage<T: 'static>(&self) -> Option<&ComponentStorage<T>> {
let id = TypeId::of::<T>();
let boxed = self.storage.get(&id)?;
boxed.as_any().downcast_ref()
}
pub fn get_storage_mut<T: 'static>(&mut self) -> Option<&mut ComponentStorage<T>> {
let id = TypeId::of::<T>();
let boxed = self.storage.get_mut(&id)?;
boxed.as_any_mut().downcast_mut()
}
pub fn get<T: 'static>(&self, id: NodeId) -> Option<&T> {
let storage = self.get_storage::<T>()?;
storage.get(&id)
}
pub fn get_mut<T: 'static>(&mut self, id: NodeId) -> Option<&mut T> {
let storage = self.get_storage_mut::<T>()?;
storage.get_mut(&id)
}
pub fn insert<T: 'static + Debug + Clone>(&mut self, id: NodeId, component: T) -> Option<T> {
if let Some(storage) = self.get_storage_mut::<T>() {
storage.insert(id, component)
} else {
let mut storage = ComponentStorage::<T>::default();
let old = storage.insert(id, component);
let id = TypeId::of::<T>();
self.storage.insert(id, Box::new(storage));
old
}
}
}
impl Clone for NodeStore {
fn clone(&self) -> Self {
let mut storage = HashMap::default();
for (id, src) in self.storage.iter() {
storage.insert(*id, src.as_ref().clone());
}
Self { storage }
}
}
pub trait ComponentStorageTrait: Any + Debug {
fn clone(&self) -> Box<dyn ComponentStorageTrait + 'static>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Clone + Debug + 'static> ComponentStorageTrait for ComponentStorage<T> {
fn clone(&self) -> Box<dyn ComponentStorageTrait + 'static> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self as _
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self as _
}
}

127
src/parse.rs Normal file
View File

@ -0,0 +1,127 @@
use crate::dom::Dom;
use crate::tag::*;
use lexpr::{Cons, Value};
use std::str::FromStr;
pub struct Parser {
vals: Vec<Value>,
}
impl Parser {
pub fn parse(document: &Value) -> Dom {
let parser = Self::from_value(document);
let mut tags = Vec::new();
for val in parser.vals.into_iter().rev() {
let mut parser = Self::from_value(&val);
let kind = parser
.parse_any_tag_kind()
.as_block()
.expect("Root tag must be a block tag");
let tag = parser.parse_block_tag(kind);
tags.push(tag);
}
Dom { tags }
}
pub fn from_value(value: &Value) -> Self {
let cons = value.as_cons().expect("Must be a cons");
Self::from_cons(cons)
}
pub fn from_cons(cons: &Cons) -> Self {
let mut vals = cons.to_vec().0;
vals.reverse();
Self { vals }
}
pub fn parse_args(&mut self) -> (Vec<(String, Value)>, Vec<Value>) {
let args = Vec::new();
let mut children = std::mem::replace(&mut self.vals, Vec::new());
children.reverse();
(args, children)
}
pub fn parse_text_tag(&mut self, kind: TextTagKind) -> TextTag {
let (_args, children_vals) = self.parse_args();
let mut children = Vec::new();
for child in children_vals.into_iter() {
let node = match child {
Value::Cons(cons) => {
let mut parser = Self::from_cons(&cons);
let kind = parser
.parse_any_tag_kind()
.as_text()
.expect("Text tag can only have other text tags as children");
TextNode::Tag(parser.parse_text_tag(kind))
}
Value::String(string) => TextNode::Text(string.to_string()),
_ => panic!("Text tag child must be a string or a cons"),
};
children.push(node);
}
TextTag { kind, children }
}
pub fn parse_leaf_tag(&mut self, kind: LeafTagKind) -> LeafTag {
let (_args, children) = self.parse_args();
if !children.is_empty() {
panic!("Leaf tag cannot have children");
}
LeafTag { kind }
}
pub fn parse_block_tag(&mut self, kind: BlockTagKind) -> BlockTag {
let (_args, children_vals) = self.parse_args();
let mut children = Vec::new();
for child in children_vals.into_iter() {
let node = match child {
Value::Cons(cons) => {
let mut parser = Self::from_cons(&cons);
match parser.parse_any_tag_kind() {
AnyTagKind::Text(kind) => BlockNode::Text(parser.parse_text_tag(kind)),
AnyTagKind::Leaf(kind) => BlockNode::Leaf(parser.parse_leaf_tag(kind)),
AnyTagKind::Block(kind) => BlockNode::Block(parser.parse_block_tag(kind)),
}
}
Value::String(string) => BlockNode::Text(TextTag {
kind: TextTagKind::Plain,
children: vec![TextNode::Text(string.to_string())],
}),
_ => panic!("Block tag child must be either a string or a cons"),
};
children.push(node);
}
BlockTag { kind, children }
}
pub fn parse_any_tag_kind(&mut self) -> AnyTagKind {
let symbol = self.parse_symbol();
AnyTagKind::from_str(&symbol).expect("Unrecognized tag")
}
pub fn parse_symbol(&mut self) -> String {
self.vals
.pop()
.as_ref()
.map(|v| v.as_symbol())
.flatten()
.expect("Expected symbol")
.to_string()
}
}

View File

@ -1,92 +0,0 @@
use std::fmt::{Debug, Display};
use std::ops::{Deref, Range};
use std::sync::Arc;
#[derive(Clone)]
pub struct WithSource<T> {
pub inner: T,
pub source: Arc<Source>,
pub range: SourceRange,
}
impl<T: Debug> Debug for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl<T: Debug + Display> Display for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, " {}", self.inner)?;
for line_idx in self.range.start.row..=self.range.end.row {
let line = &self.source.lines[line_idx];
writeln!(f, " {:<4}| {}", line_idx + 1, line)?;
let start = if line_idx == self.range.start.row {
self.range.start.col
} else {
0
};
let end = if line_idx == self.range.end.row {
self.range.end.col
} else {
line.len()
};
let spaces: String = std::iter::repeat(" ").take(start).collect();
let carats: String = std::iter::repeat("^").take(end - start).collect();
writeln!(f, " | {}{}", spaces, carats)?;
}
Ok(())
}
}
impl<T> AsRef<T> for WithSource<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T> Deref for WithSource<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T> WithSource<T> {
pub fn map<O>(&self, other: O) -> WithSource<O> {
WithSource {
inner: other,
source: self.source.to_owned(),
range: self.range.clone(),
}
}
}
#[derive(Clone, Debug)]
pub struct Source {
pub full: String,
pub lines: Vec<String>,
}
impl Source {
pub fn from_string(full: String) -> Self {
Self {
lines: full.lines().map(ToString::to_string).collect(),
full,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SourcePosition {
pub row: usize,
pub col: usize,
}
pub type SourceRange = Range<SourcePosition>;

36
src/style.rs Normal file
View File

@ -0,0 +1,36 @@
use ansi_term::Style;
#[derive(Clone, Debug)]
pub struct Stylesheet {
pub header_style_float: Style,
pub header_style_floor: Style,
pub header_prefix: String,
pub p: Style,
pub underline: Style,
pub bold: Style,
pub italic: Style,
pub strikethrough: Style,
pub bullet: Style,
pub ul_prefix: String,
pub list_indent: usize,
}
impl Default for Stylesheet {
fn default() -> Self {
use ansi_term::Color::*;
Self {
header_style_float: Blue.bold(),
header_style_floor: Blue.bold().underline(),
header_prefix: "#".to_string(),
p: Style::new(),
underline: Style::new().underline(),
bold: Style::new().bold(),
italic: Style::new().italic(),
strikethrough: Style::new().strikethrough(),
bullet: Black.into(),
ul_prefix: "*".to_string(),
list_indent: 4,
}
}
}

114
src/tag.rs Normal file
View File

@ -0,0 +1,114 @@
use std::str::FromStr;
use strum::EnumString;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum TextTagKind {
Plain,
Italic,
Bold,
Underline,
Strikethrough,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum TextNode {
Text(String),
Tag(TextTag),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct TextTag {
pub kind: TextTagKind,
pub children: Vec<TextNode>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum LeafTagKind {
Figlet,
Code,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct LeafTag {
pub kind: LeafTagKind,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BlockTagKind {
H1,
H2,
H3,
P,
Ul,
Ol,
Li,
Div,
Table,
Tr,
Td,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum BlockNode {
Text(TextTag),
Leaf(LeafTag),
Block(BlockTag),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlockTag {
pub kind: BlockTagKind,
pub children: Vec<BlockNode>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum AnyTagKind {
Text(TextTagKind),
Leaf(LeafTagKind),
Block(BlockTagKind),
}
impl AnyTagKind {
pub fn as_text(self) -> Option<TextTagKind> {
if let AnyTagKind::Text(text) = self {
Some(text)
} else {
None
}
}
pub fn as_leaf(self) -> Option<LeafTagKind> {
if let AnyTagKind::Leaf(leaf) = self {
Some(leaf)
} else {
None
}
}
pub fn as_block(self) -> Option<BlockTagKind> {
if let AnyTagKind::Block(block) = self {
Some(block)
} else {
None
}
}
}
impl FromStr for AnyTagKind {
type Err = strum::ParseError;
fn from_str(string: &str) -> Result<Self, strum::ParseError> {
if let Ok(text) = TextTagKind::from_str(string) {
Ok(AnyTagKind::Text(text))
} else if let Ok(leaf) = LeafTagKind::from_str(string) {
Ok(AnyTagKind::Leaf(leaf))
} else if let Ok(block) = BlockTagKind::from_str(string) {
Ok(AnyTagKind::Block(block))
} else {
Err(strum::ParseError::VariantNotFound)
}
}
}

View File

@ -1,352 +0,0 @@
use std::str::FromStr;
use strum::EnumString;
use crate::ast;
use crate::block::BlockTagKind;
use crate::layout::{RenderedBlock, Dimensions};
use crate::source::WithSource;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum TextTagKind {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Italic,
Bold,
Underline,
Strike,
Style,
}
impl TextTagKind {
pub fn to_color(&self) -> Option<Color> {
use Color as C;
use TextTagKind::*;
match self {
Black => Some(C::Black),
Red => Some(C::Red),
Green => Some(C::Green),
Yellow => Some(C::Yellow),
Blue => Some(C::Blue),
Magenta => Some(C::Magenta),
Cyan => Some(C::Cyan),
White => Some(C::White),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl crate::args::ParseSymbol for Color {}
impl From<Color> for ansi_term::Color {
fn from(c: Color) -> Self {
use ansi_term::Color as C;
use Color::*;
match c {
Black => C::Black,
Red => C::Red,
Green => C::Green,
Yellow => C::Yellow,
Blue => C::Blue,
Magenta => C::Purple,
Cyan => C::Cyan,
White => C::White,
}
}
}
bitflags::bitflags! {
pub struct TextStyleFlags: u32 {
const ITALIC = 1 << 0;
const BOLD = 1 << 1;
const UNDERLINE = 1 << 2;
const STRIKETHROUGH = 1 << 3;
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct TextStyle {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub flags: TextStyleFlags,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
fg: None,
bg: None,
flags: TextStyleFlags::empty(),
}
}
}
impl TextStyle {
pub fn apply_tag(&mut self, kind: &TextTagKind) {
if let Some(color) = kind.to_color() {
self.fg = Some(color);
return;
}
use TextStyleFlags as F;
use TextTagKind::*;
let flags = match kind {
Italic => F::ITALIC,
Bold => F::BOLD,
Strike => F::STRIKETHROUGH,
Underline => F::UNDERLINE,
_ => unreachable!(),
};
self.flags |= flags;
}
pub fn inherit(&self, base: &TextStyle) -> Self {
Self {
fg: self.fg.or(base.fg),
bg: self.bg.or(base.bg),
flags: self.flags | base.flags,
}
}
pub fn make_painter(&self) -> ansi_term::Style {
ansi_term::Style {
foreground: self.fg.map(Into::into),
background: self.bg.map(Into::into),
is_bold: self.flags.contains(TextStyleFlags::BOLD),
is_italic: self.flags.contains(TextStyleFlags::ITALIC),
is_underline: self.flags.contains(TextStyleFlags::UNDERLINE),
is_strikethrough: self.flags.contains(TextStyleFlags::STRIKETHROUGH),
..Default::default()
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct StyledString {
pub string: String,
pub style: TextStyle,
}
impl StyledString {
pub fn from_plain(plain: &str) -> Self {
Self {
string: plain.to_string(),
style: TextStyle::default(),
}
}
pub fn render(&self, base: &TextStyle) -> String {
let painter = self.style.inherit(base).make_painter();
painter.paint(&self.string).to_string()
}
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct TaggedText {
pub strings: Vec<StyledString>,
}
impl TaggedText {
pub fn from_plain(plain: &str) -> Self {
Self {
strings: vec![StyledString::from_plain(plain)],
}
}
pub fn concat(&mut self, mut other: Self) {
self.strings.append(&mut other.strings);
}
pub fn parse(style: &TextStyle, vals: &[ast::Value]) -> Result<Self, TextParseError> {
let mut text = Self::default();
for val in vals.iter() {
match &val.inner {
ast::ValueKind::String(string) => text.strings.push(StyledString {
string: string.clone(),
style: style.clone(),
}),
ast::ValueKind::List(items) => text.concat(Self::parse(style, items)?),
ast::ValueKind::Tag(tag) => text.concat(Self::parse_tag(style, tag)?),
_ => return Err(val.map(TextParseErrorKind::UnexpectedValue)),
}
}
Ok(text)
}
pub fn parse_tag(style: &TextStyle, tag: &ast::Tag) -> Result<Self, TextParseError> {
let kind = TextTagKind::from_str(&tag.id)
.map_err(|_| tag.id.map(TextParseErrorKind::InvalidTag))?;
Self::parse_tag_body(style, &kind, &tag.body)
}
pub fn parse_tag_body(
style: &TextStyle,
kind: &TextTagKind,
body: &ast::TagBody,
) -> Result<Self, TextParseError> {
let mut style = style.clone();
style.apply_tag(kind);
Self::parse(&style, &body.children)
}
pub fn render(&self, base: &TextStyle) -> String {
let mut rendered = String::new();
for string in self.strings.iter() {
let string = string.render(base);
rendered.push_str(&string);
}
rendered
}
pub fn make_text_block(&self, kind: BlockTagKind) -> TextBlock {
use BlockTagKind::*;
match kind {
P => TextBlock {
text: self.to_owned(),
initial_indent: None,
subsequent_indent: None,
},
_ => unimplemented!("Text block for {:?} tag", kind),
}
}
}
pub fn parse_plain(vals: &[ast::Value]) -> Result<String, TextParseError> {
let mut text = String::new();
for val in vals.iter() {
match &val.inner {
ast::ValueKind::List(vals) => text.push_str(&parse_plain(vals)?),
ast::ValueKind::String(string) => text.push_str(&string),
ast::ValueKind::Tag(_) => return Err(val.map(TextParseErrorKind::UnexpectedTag)),
_ => return Err(val.map(TextParseErrorKind::UnexpectedValue)),
}
}
Ok(text)
}
pub type TextParseError = WithSource<TextParseErrorKind>;
#[derive(Clone, Debug)]
pub enum TextParseErrorKind {
InvalidTag,
UnexpectedValue,
UnexpectedTag,
}
impl std::fmt::Display for TextParseErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Text parse error: ")?;
use TextParseErrorKind::*;
match self {
InvalidTag => write!(f, "invalid tag"),
UnexpectedValue => write!(f, "unexpected value"),
UnexpectedTag => write!(f, "unexpected tag"),
}
}
}
#[derive(Clone, Debug)]
pub struct TextBlock {
pub initial_indent: Option<StyledString>,
pub subsequent_indent: Option<StyledString>,
pub text: TaggedText,
}
impl TextBlock {
// FIXME this function discards tag information
pub fn layout(&self, base: &TextStyle, width: usize) -> TextLayout {
let text = self.text.render(&base);
let initial_indent = self
.initial_indent
.as_ref()
.map(|text| text.render(&base))
.unwrap_or("".to_string());
let subsequent_indent = self
.subsequent_indent
.as_ref()
.map(|text| text.render(&base))
.unwrap_or("".to_string());
let options = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(&subsequent_indent);
TextLayout {
lines: textwrap::wrap(&text, options)
.into_iter()
.map(|line| TaggedText {
strings: vec![StyledString::from_plain(&line)],
})
.collect(),
width,
}
}
}
#[derive(Clone, Debug)]
pub struct TextLayout {
pub lines: Vec<TaggedText>,
pub width: usize,
}
impl TextLayout {
pub fn from_plain(plain: &str) -> Self {
let mut lines = Vec::new();
let mut width = 0;
for line in plain.lines() {
width = width.max(textwrap::core::display_width(&line));
lines.push(TaggedText::from_plain(line));
}
Self { lines, width }
}
pub fn render(&self, base: &TextStyle) -> RenderedBlock {
let mut lines = Vec::new();
for line in self.lines.iter() {
let mut line = line.render(base);
let used_width = textwrap::core::display_width(&line);
let needed_width = self.width - used_width.min(self.width);
line.extend(std::iter::repeat(' ').take(needed_width));
lines.push(line);
}
RenderedBlock {
size: Dimensions {
width: self.width,
height: lines.len(),
},
lines,
}
}
}